Nově zavedené termíny
Transkript
Myslíme objektově v jazyku Rudolf Pecinovský 2004 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 1 z 433 Stručný obsah Stručný obsah Stručný obsah ...............................................................................................................2 Podrobný obsah ............................................................................................................4 Úvod .............................................................................................................................13 Část 1: 1. 2. 3. 4. 5. 239 Přemýšlíme 395 Program začíná přemýšlet ................................................................................396 Ještě jednu rundu, prosím................................................................................401 Kontejnery nejsou jen na odpadky ..................................................................402 Pole.....................................................................................................................403 Jak nebýt výjimečný..........................................................................................404 Co jsme si ještě neřekli .....................................................................................405 Část 4: A B C D Více tváří Rozhraní .............................................................................................................240 Co takhle něco zdědit?......................................................................................291 Třídy mohou také dědit .....................................................................................314 Budete si to přát zabalit? ..................................................................................376 Knihovny ............................................................................................................393 Část 3: 11. 12. 13. 14. 15. 16. 18 Seznamujeme se s nástroji.................................................................................19 Pracujeme s třídami a objekty ............................................................................40 Vytváříme vlastní třídu ........................................................................................80 Dotváříme vlastní třídu......................................................................................176 Návrhové vzory..................................................................................................229 Část 2: 6. 7. 8. 9. 10. Zapouzdření Přílohy 406 Instalace vývojového prostředí ........................................................................407 Základy práce s BlueJ .......................................................................................414 Syntaktické diagramy........................................................................................416 Použité projekty.................................................................................................417 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 2 z 433 Rejstřík.......................................................................................................................420 Část 5: KONEC 424 17. Číslování řádků programu ................................................................................425 18. Odkladky ............................................................................................................432 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 3 z 433 Podrobný obsah Podrobný obsah Stručný obsah ...............................................................................................................2 Podrobný obsah ............................................................................................................4 Úvod .............................................................................................................................13 Použité konvence ................................................................................................................... 16 Část 1: 1. Zapouzdření 18 Seznamujeme se s nástroji.................................................................................19 1.1 Trochu historie .............................................................................................................. 19 První počítače.................................................................................................................................. 19 Program musí být především spolehlivý ...................................................................................... 20 1.2 Objektově orientované programování – OOP............................................................. 21 Vývoj metodik programování ......................................................................................................... 21 Principy OOP ................................................................................................................................... 22 1.3 Překladače, interprety, platformy................................................................................. 23 Operační systém a platforma ......................................................................................................... 23 Programovací jazyky....................................................................................................................... 24 1.4 Java a její zvláštnosti.................................................................................................... 25 Objektově orientovaná ............................................................................................................... 26 Jednoduchá ................................................................................................................................ 26 Překládaná i interpretovaná ....................................................................................................... 26 Multiplatformní ............................................................................................................................ 27 1.5 Vývojové prostředí BlueJ ............................................................................................. 28 1.6 Projekty a BlueJ............................................................................................................. 29 Umístění projektů na disku ............................................................................................................ 29 Windows a substituované disky .................................................................................................... 30 Vyhledání a otevření projektu ........................................................................................................ 32 1.7 Diagram tříd ................................................................................................................... 33 Manipulace s třídami v diagramu................................................................................................... 34 1.8 Shrnutí – co jsme se naučili ......................................................................................... 38 Nově zavedené termíny .................................................................................................................. 39 2. Pracujeme s třídami a objekty ............................................................................40 2.1 Nejprve trocha teorie..................................................................................................... 40 Třídy a jejich instance..................................................................................................................... 41 Zprávy............................................................................................................................................... 41 Metody .............................................................................................................................................. 42 2.2 Analogie ......................................................................................................................... 43 2.3 Třídy a jejich instance ................................................................................................... 43 Vytváříme svou první instanci ....................................................................................................... 43 Pravidla pro tvorbu identifikátorů v jazyce Java.......................................................................... 46 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 4 z 433 Vytváříme svou první instanci – pokračování ..............................................................................47 Posíláme instanci zprávu................................................................................................................49 Vytváříme další instance.................................................................................................................50 Rušení instancí a správa paměti....................................................................................................50 2.4 Zprávy žádající o hodnotu.............................................................................................52 Datové typy ......................................................................................................................................52 Primitivní datové typy..................................................................................................................53 Objektové datové typy ................................................................................................................54 Vracení hodnot primitivních typů ..................................................................................................54 Vracení hodnot objektových typů..................................................................................................55 2.5 Parametry a jejich typy ..................................................................................................58 Vyvolání konstruktoru s parametry ...............................................................................................59 Parametry objektových typů ..........................................................................................................62 Posílání zpráv s parametry .............................................................................................................63 2.6 Metody třídy....................................................................................................................64 2.7 Instance versus odkaz...................................................................................................66 2.8 Výlet do nitra instancí....................................................................................................68 Atributy instancí ..............................................................................................................................68 Opravit ........................................................................................................................................70 Atributy třídy – statické atributy ....................................................................................................70 Opravit ........................................................................................................................................71 2.9 Přímé zadávání hodnot parametrů objektových typů.................................................73 Veřejné atributy................................................................................................................................73 Odkazy vrácené po zaslání zprávy ................................................................................................75 2.10 Shrnutí – co jsme se naučili..........................................................................................77 Nově zavedené termíny (abecedně)...............................................................................................79 3. Vytváříme vlastní třídu ........................................................................................80 3.1 První vlastní třída ...........................................................................................................82 3.2 Zdrojový kód třídy..........................................................................................................84 Prázdná třída ....................................................................................................................................85 Implicitní konstruktor ......................................................................................................................87 3.3 3.4 3.5 3.6 Odstranění třídy .............................................................................................................88 Přejmenování třídy.........................................................................................................89 Bezparametrický konstruktor........................................................................................91 Ladění..............................................................................................................................95 Syntaktické chyby ...........................................................................................................................96 Běhové chyby ..................................................................................................................................97 Logické (sémantické) chyby...........................................................................................................99 3.7 Konstruktor s parametry .............................................................................................100 Konstruktor this.............................................................................................................................101 3.8 Testování ......................................................................................................................104 TDD – vývoj řízený testy ...............................................................................................................104 Testovací třída ...............................................................................................................................105 Přípravek ........................................................................................................................................106 Úprava obsahu přípravku .............................................................................................................107 3.9 Deklarace atributů........................................................................................................109 Modifikátory přístupu....................................................................................................................110 Vylepšujeme Strom .......................................................................................................................110 Možné důsledky zveřejnění atributů............................................................................................112 3.10 Syntaktické definice.....................................................................................................113 3.11 Definujeme vlastní metodu..........................................................................................115 Test vytvořených metod ...............................................................................................................117 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 5 z 433 Nejprve testy, pak program?........................................................................................................ 120 Někdy jsou věci složitější ............................................................................................................. 123 Použití metod vracejících hodnotu.............................................................................................. 124 Definice metod vracejících hodnotu............................................................................................ 125 Parametry a návratové hodnoty objektových typů.................................................................... 126 3.12 Přetěžování .................................................................................................................. 127 3.13 Zapouzdření ................................................................................................................. 128 Rozhraní × implementace............................................................................................................. 128 Přístupové metody ........................................................................................................................ 129 Konvence pro názvy přístupových metod ................................................................................ 131 Kontrakt.......................................................................................................................................... 132 3.14 Kvalifikace a klíčové slovo this.................................................................................. 132 Kvalifikace metod.......................................................................................................................... 132 Kvalifikace atributů ....................................................................................................................... 134 3.15 Atributy a metody třídy (statické atributy a metody)................................................ 136 Atributy třídy .................................................................................................................................. 136 Metody třídy ................................................................................................................................... 137 3.16 Lokální proměnné........................................................................................................ 139 3.17 Konstanty a literály ..................................................................................................... 143 Konstanty objektových typů ........................................................................................................ 145 Správná podoba literálů ............................................................................................................... 146 boolean..................................................................................................................................... 146 int.............................................................................................................................................. 146 double....................................................................................................................................... 146 String ........................................................................................................................................ 147 null ............................................................................................................................................ 148 3.18 Komentáře a dokumentace......................................................................................... 148 Tři druhy komentářů ..................................................................................................................... 149 Uspořádání jednotlivých prvků v těle třídy................................................................................. 157 BlueJ a komentářová nápověda .................................................................................................. 158 Automaticky generovaná dokumentace ..................................................................................... 160 Dokumentace celého projektu ..................................................................................................... 161 Pomocné značky pro tvorbu dokumentace................................................................................ 164 @author.................................................................................................................................... 164 @version .................................................................................................................................. 164 @param.................................................................................................................................... 164 @returns................................................................................................................................... 164 3.19 Závěrečný příklad – UFO ............................................................................................ 165 Třída Dispečer................................................................................................................................ 166 Jednodušší varianta ................................................................................................................. 166 Varianta ovládaná z klávesnice................................................................................................ 166 Třída UFO ....................................................................................................................................... 167 Třída UFOTest................................................................................................................................ 168 3.20 Vytvoření samostatné aplikace .................................................................................. 168 Třída spouštějící aplikaci.............................................................................................................. 169 Vytvoření souboru JAR s aplikací ............................................................................................... 169 3.21 Shrnutí – co jsme se v kapitole naučili...................................................................... 171 4. Dotváříme vlastní třídu......................................................................................176 4.1 Jednoduché vstupy a výstupy ................................................................................... 176 Doplnění projektu o třídu odjinud................................................................................................ 177 Textové řetězce.............................................................................................................................. 177 Rozdíl mezi prázdným řetězcem a null .................................................................................... 179 Čísla ................................................................................................................................................ 180 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 6 z 433 4.2 Knihovny statických metod.........................................................................................181 4.3 Podrobnosti o operátorech .........................................................................................181 Binární aritmetické operátory + – * / %..............................................................................182 Sčítání, odčítání, násobení.......................................................................................................182 Slučování řetězců + ..................................................................................................................183 Dělení / .....................................................................................................................................183 Zbytek po dělení (dělení modulo) %.........................................................................................184 Unární operátory + –..................................................................................................................185 Kulaté závorky ( ) .........................................................................................................................185 Přiřazovací operátor =...................................................................................................................186 Sdružené přiřazovací operátory +=, –=, *=, /=, %= .....................................................................187 Operátor přetypován (typ) ............................................................................................................188 Pseudopřetypování na String ...................................................................................................189 4.4 Počítáme instance........................................................................................................190 4.5 Inkrementační a dekrementační operátory ................................................................191 Odbočka o obecných zásadách programování..........................................................................194 Jiný způsob inicializace rodného čísla .......................................................................................196 4.6 4.7 4.8 4.9 Standardní výstup........................................................................................................196 Metoda toString()..........................................................................................................198 Prázdná standardní třída .............................................................................................199 V útrobách testovací třídy ...........................................................................................202 Přípravek ........................................................................................................................................204 Automaticky generované testy ....................................................................................................206 Vlastní testy....................................................................................................................................207 Úklid ................................................................................................................................................208 Metody assertEquals a assertTrue ..............................................................................................208 Test testů........................................................................................................................................209 4.10 Debugger a práce s ním...............................................................................................211 Krokování programu .....................................................................................................................212 Okno debuggeru ............................................................................................................................215 Vlákna.......................................................................................................................................216 Pořadí volání.............................................................................................................................216 Atributy třídy..............................................................................................................................217 Atributy instancí ........................................................................................................................217 Lokální proměnné .....................................................................................................................217 Atributy a proměnné objektových typů.......................................................................................218 Už nezastavuj – ruším zarážky .....................................................................................................220 Předčasný konec programu .........................................................................................................220 Pozastavení běžícího programu...................................................................................................221 Krokování konstruktorů................................................................................................................221 4.11 Hodnotové a referenční typy.......................................................................................222 Hodnotové typy..............................................................................................................................222 Referenční datové typy ................................................................................................................. 223 Program demonstrující rozdíl.......................................................................................................223 4.12 Projekt Zlomky .............................................................................................................225 4.13 Shrnutí – co jsme se naučili........................................................................................226 Nové termíny ..................................................................................................................................228 5. Návrhové vzory..................................................................................................229 Opravit ......................................................................................................................................229 5.2 Přepravka (Messenger)................................................................................................230 5.3 Jedináček (Singleton) ..................................................................................................233 5.4 Výčtové typy .................................................................................................................237 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 7 z 433 5.5 Shrnutí – co jsme se naučili ....................................................................................... 237 Nové termíny.................................................................................................................................. 238 Část 2: 6. Více tváří 239 Rozhraní .............................................................................................................240 6.1 6.2 6.3 6.4 Kreslíme jinak .............................................................................................................. 241 Rozhraní jako zvláštní druh třídy ............................................................................... 242 Instance rozhraní......................................................................................................... 244 Nový projekt................................................................................................................. 244 Práce s novým plátnem ................................................................................................................ 248 Událostmi řízené programování................................................................................................... 250 6.5 Implementace rozhraní ............................................................................................... 250 Implementace rozhraní v diagramu tříd ...................................................................................... 251 Implementace rozhraní ve zdrojovém kódu ............................................................................... 252 6.6 Úprava zdrojového kódu třídy Strom ........................................................................ 252 Třída musí jít přeložit .................................................................................................................... 253 Testování........................................................................................................................................ 256 Závěrečné úpravy.......................................................................................................................... 261 Uložení odkazu na Plátno do atributu třídy .............................................................................. 261 Odstranění statického atributu krok.......................................................................................... 262 Úpravy posunových metod....................................................................................................... 262 Zefektivnění přesunu................................................................................................................ 262 Efektivita vykreslování.................................................................................................................. 263 6.7 Implementace několika rozhraní ................................................................................ 264 Odvolání implementace rozhraní................................................................................................. 265 6.8 Návrhový vzor Služebník (Servant) ........................................................................... 266 Proč rozhraní ................................................................................................................................. 267 Implementace................................................................................................................................. 268 Aplikace na náš projekt ................................................................................................................ 269 Závěrečný test ............................................................................................................................... 270 6.9 Refaktorování............................................................................................................... 272 Ukázka ............................................................................................................................................ 273 1. krok: Vytvoření testu............................................................................................................. 274 2. krok: Definice nových atributů .............................................................................................. 275 3. krok: Kopírování těla konstruktoru do těla metody............................................................... 276 4. krok: Dočasné „odkonstatnění“ některých atributů............................................................... 277 5. krok: Definice potřebných lokálních proměnných................................................................. 277 6. krok: Odstranění tvorby nových instancí koruny a kmene ................................................... 277 7. krok: Vrácení koruny a kmene mezi konstanty .................................................................... 278 8. krok: Vyvolání metody setRozměr(int,int) v konstruktoru..................................................... 278 9. krok: Odstranění zdvojeného kódu z konstruktoru............................................................... 278 10. krok: Doplnění metody setRozměr(Rozměr)...................................................................... 279 11. krok: Doplnění metody setOblast(Oblast) .......................................................................... 279 6.10 Projekt Výtah................................................................................................................ 280 Analýza problému.......................................................................................................................... 281 Okolí ......................................................................................................................................... 281 Konstruktory ............................................................................................................................. 281 Potřebné metody ...................................................................................................................... 282 Implementace................................................................................................................................. 283 Implementovaná rozhraní......................................................................................................... 284 Atributy ..................................................................................................................................... 284 Postup při návrhu metod .......................................................................................................... 285 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 8 z 433 Metoda doPatra(int) ..................................................................................................................285 Metoda přijeďK(IPosuvný) ........................................................................................................285 Metoda nástup(IPosuvný).........................................................................................................286 Metody výstupVpravo() a výstupVlevo()...................................................................................286 Test převozu pasažéra .............................................................................................................286 Metody odvezVpravo(IPosuvný,int) a odvezVlevo(IPosuvný,int) .............................................287 6.11 Shrnutí – co jsme se naučili........................................................................................288 Nové termíny ..................................................................................................................................290 7. Co takhle něco zdědit? .....................................................................................291 7.1 Co to je, když rozhraní dědí? ......................................................................................292 7.2 Jak to zařídit .................................................................................................................293 Duplicitně deklarovaná implementace ........................................................................................294 7.3 Společný potomek několika rozhraní.........................................................................294 7.4 Návrhový vzor Stav (State)..........................................................................................298 Projekt Šipky ..................................................................................................................................298 Shrnutí ............................................................................................................................................303 Projekt Robot .................................................................................................................................304 7.5 Návrhový vzor Zástupce (Proxy) ................................................................................304 7.6 Projekt Kabina ..............................................................................................................306 Předpřipravené třídy......................................................................................................................307 Multipřesouvač..........................................................................................................................307 IMultiposuvný............................................................................................................................307 IZastávka ..................................................................................................................................308 Linka .........................................................................................................................................308 Úloha – Kabina...............................................................................................................................309 Projekt Šipky ................................................................................... Chyba! Záložka není definována. Shrnutí ............................................................................................. Chyba! Záložka není definována. Projekt Robot .................................................................................. Chyba! Záložka není definována. 7.7 Shrnutí – co jsme se naučili........................................................................................313 Nově zavedené termíny.................................................................................................................313 8. Třídy mohou také dědit .....................................................................................314 8.1 Podtřídy a nadtřídy ......................................................................................................315 Specializace ...................................................................................................................................315 Zobecnění.......................................................................................................................................315 Realizace v OOP ............................................................................................................................316 Univerzální (pra)rodič....................................................................................................................317 8.2 Experimenty s dědičností............................................................................................318 Univerzální rodič Object................................................................................................................319 Atributy a bezparametrické konstruktory tříd v projektu ..........................................................320 Hierarchie dědičnosti ....................................................................................................................322 Podobjekt rodičovské třídy ..........................................................................................................324 Explicitní volání konstruktoru předka .........................................................................................327 Chráněné atributy – modifikátor přístupu protected .................................................................329 Dědičnost a metody tříd................................................................................................................330 Metody instancí, jejich dědění a překrývaní ...............................................................................331 Nové metody.............................................................................................................................332 Nepřekryté zděděné metody.....................................................................................................332 Překryté zděděné metody.........................................................................................................332 Test chování překrývajících a překrytých metod .......................................................................334 Porovnání .................................................................................................................................337 Podobjekt..................................................................................................................................337 Soukromá metoda ....................................................................................................................337 Veřejná metoda ........................................................................................................................338 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 9 z 433 Instance vnučka........................................................................................................................ 338 Vyvolání překryté verze metody .................................................................................................. 338 8.3 Vytváříme dceřinou třídu ............................................................................................ 340 Jednoduchá dceřiná třída............................................................................................................. 341 Konstruktory potomka.................................................................................................................. 343 Složitější dceřiná třída .................................................................................................................. 344 Příprava prázdné třídy a testovací třídy ................................................................................... 344 Definice konstruktorů................................................................................................................ 345 Metoda kresli(java.awt.Graphics2D) ........................................................................................ 346 Metoda setPozice(int,int).......................................................................................................... 346 Jak přesvědčit objekt, aby se pokaždé choval jinak ................................................................. 349 Samostatná úloha: Terč................................................................................................................ 351 8.4 Vytváříme rodičovskou třídu ...................................................................................... 354 Společný rodič Posuvný............................................................................................................... 354 Příprava .................................................................................................................................... 354 Konstantní atributy třídy ........................................................................................................... 356 Proměnné atributy třídy ............................................................................................................ 356 Konstantní atributy instancí ...................................................................................................... 356 Proměnné atributy instancí....................................................................................................... 357 Konstruktory ............................................................................................................................. 357 Metody instancí ........................................................................................................................ 358 Doladění dceřiných tříd ................................................................................................................ 359 Elipsa, Obdélník, Trojúhelník ................................................................................................... 359 Čára.......................................................................................................................................... 360 Text........................................................................................................................................... 360 Strom ........................................................................................................................................ 361 Společný rodič Hýbací.................................................................................................................. 362 8.5 Abstraktní metody a třídy ........................................................................................... 363 Neimplementovaná metoda implementovaného rozhraní ........................................................ 364 Nově deklarovaná abstraktní metoda.......................................................................................... 365 Abstraktní třída bez abstraktních metod..................................................................................... 366 8.6 Návrhový vzor Stav podruhé...................................................................................... 367 Projekt Šipka.................................................................................................................................. 367 8.7 Co je na dědičnosti špatné ......................................................................................... 369 8.8 Kdy (ne)použít dědičnost............................................................................................ 370 Co jsme dělali špatně.................................................................................................................... 371 Kdy dát přednost skládání a kdy dědičnosti .............................................................................. 372 8.9 Shrnutí – co jsme se naučili ....................................................................................... 372 Nově zavedené termíny ................................................................................................................ 374 9. Budete si to přát zabalit? ..................................................................................376 9.1 Velké programy a jejich problémy ............................................................................. 376 9.2 Balíčky.......................................................................................................................... 377 Podbalíčky...................................................................................................................................... 378 Uspořádání podbalíčků s programy k dosavadní části knihy .................................................. 379 9.3 Balíčky a BlueJ ............................................................................................................ 380 Příprava stromu balíčků pro BlueJ ve správci souborů............................................................ 380 9.4 Hrajeme si s balíčky .................................................................................................... 381 Rodičovský balíček v diagramu tříd............................................................................................ 383 Převedení obyčejné složky na balíček v BlueJ .......................................................................... 384 Vytvoření nového balíčku v BlueJ ............................................................................................... 384 Putování stromem balíčků............................................................................................................ 384 Automatická redefinice příkazu package.................................................................................... 385 9.5 Jmenné prostory a příkaz import............................................................................... 386 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 10 z 433 Import více tříd...............................................................................................................................386 Podbalíčky......................................................................................................................................387 Balíček java.lang............................................................................................................................388 Nevýhody koncepce balíčků v BlueJ...........................................................................................388 Přemosťovací třída........................................................................................................................388 9.6 9.7 9.8 9.9 Přístupová práva v rámci balíčku ...............................................................................389 Tvorba vlastních aplikací.............................................................................................390 Knihovny.......................................................................................................................391 Shrnutí – co jsme se naučili........................................................................................391 Nové termíny ..................................................................................................................................392 10. Knihovny ............................................................................................................393 10.1 10.2 10.3 10.4 10.5 Pracujeme s náhodou..................................................................................................393 Návrhový vzor Adaptér................................................................................................393 Pracujeme s obrázky ...................................................................................................394 Přetypovávání na rodiče a na potomka......................................................................394 Shrnutí – co jsme se naučili........................................................................................394 Nově zavedené termíny.................................................................................................................394 Část 3: Přemýšlíme 395 11. Program začíná přemýšlet................................................................................396 11.1 Jednoduché rozhodování............................................................................................396 Třídy jako objekty ..........................................................................................................................398 11.2 11.3 11.4 11.5 11.6 Výběr ze dvou možností ..............................................................................................399 Když – jinak ..................................................................................................................399 Násobný výběr, ............................................................................................................399 Přepínač ........................................................................................................................399 Shrnutí – co jsme se naučili........................................................................................399 Nově zavedené termíny.................................................................................................................400 12. Ještě jednu rundu, prosím................................................................................401 12.1 Podkapitola...................................................................................................................401 12.2 Shrnutí – co jsme se naučili........................................................................................401 Nové termíny ..................................................................................................................................401 13. Kontejnery nejsou jen na odpadky ..................................................................402 13.1 Podkapitola...................................................................................................................402 13.2 Shrnutí – co jsme se naučili........................................................................................402 Nově zavedené termíny.................................................................................................................402 14. Pole.....................................................................................................................403 14.1 Podkapitola...................................................................................................................403 14.2 Shrnutí – co jsme se naučili........................................................................................403 Nově zavedené termíny.................................................................................................................403 15. Jak nebýt výjimečný..........................................................................................404 15.1 Podkapitola...................................................................................................................404 15.2 Shrnutí – co jsme se naučili........................................................................................404 Nově zavedené termíny.................................................................................................................404 16. Co jsme si ještě neřekli.....................................................................................405 16.1 Podkapitola...................................................................................................................405 16.2 Shrnutí – co jsme se naučili........................................................................................405 Nově zavedené termíny.................................................................................................................405 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 11 z 433 Část 4: A Přílohy 406 Instalace vývojového prostředí ........................................................................407 A.1 Instalace Java 2 SDK................................................................................................... 407 A.2 Instalace prostředí BlueJ............................................................................................ 409 Instalace prostředí......................................................................................................................... 409 Instalace rozšíření ......................................................................................................................... 409 První spuštění................................................................................................................................ 410 Konfigurace BlueJ......................................................................................................................... 411 B C D Základy práce s BlueJ .......................................................................................414 Syntaktické diagramy........................................................................................416 Použité projekty.................................................................................................417 02_Objekty ............................................................................................................................... 417 03_Třídy_A ............................................................................................................................... 417 03_Třídy_Z ............................................................................................................................... 417 03_UFO .................................................................................................................................... 417 04_Zlomky ................................................................................................................................ 418 05_Vzory .................................................................................................................................. 418 06_Rozhraní_A......................................................................................................................... 418 06_Rozhraní_Z......................................................................................................................... 418 07_Dědění_rozhraní_Z ............................................................................................................ 418 08_Dědičnost_tříd_pokusy....................................................................................................... 418 08_Dědičnost_tříd_A................................................................................................................ 418 08_Dědičnost_tříd_B................................................................................................................ 419 08_Dědičnost_tříd_C................................................................................................................ 419 08_Dědičnost_tříd_D................................................................................................................ 419 08_Dědičnost_tříd_E................................................................................................................ 419 Rejstřík .......................................................................................................................420 Část 5: KONEC 424 17. Číslování řádků programu ................................................................................425 18. Odkladky ............................................................................................................432 18.1 Podkapitola .................................................................................................................. 432 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 12 z 433 Úvod Úvod Otevíráte knížku, která vás chce naučit programovat moderním, objektově orientovaným stylem. Rozhodnete-li se s její pomocí vstoupit do světa programování, naučíte se vytvářet programy tak, abyste se za ně nemuseli ani po několika letech stydět. Už podle její tloušťky jste nejspíše odhadli, že není určena těm, kteří hledají dvousetstránkovou rychloučebnici, s jejíž pomocí se naučí programovat za víkend. Jestli jste tuto knihu otevřeli, tak asi patříte k těm, kteří vědí, že taková učebnice neexistuje. Dvousetstránková knížka bude možná levná, ale může věci pouze naznačovat, takže se její čtenář dostane ke skutečnému poznání až po dlouhém usilovném samostudiu. Tato knížka je určena pro ty, kteří to se svoji touhou naučit se programovat myslí vážně a chtějí se naučit programovat dobře. Nejsem přítelem stručných náznaků. Naopak, budu se v ní snažit vám předvést všechny klíčové dovednosti krok za krokem a ukázat vám úskalí, která vás mohou očekávat při tvorbě vašich vlastních programů. Musím vás také upozornit na to, že tato knížka se od běžných učebnic, s nimiž se můžete v současné době v knihkupectví setkat, poněkud liší. Současné učebnice programování jsou totiž většinou především učebnicemi nějakého programovacího jazyka. Jejich autoři se proto ve svém výkladu soustředí hlavně na výklad vlastností popisovaného jazyka a snaží se jich čtenářům vysvětlit co nejvíce. Předpokládají přitom, že vedlejším produktem jejich výkladu bude to, že se čtenář naučí současně programovat. Mé letité zkušenosti s programátory, které přeučuji z klasického programování na programování objektově orientované, však ukazují, že tohoto výsledku bývá dosaženo jen zřídka. Většina programátorů, kteří přicházejí do mých kurzů, zná poměrně dobře konstrukce svého programovacího jazyka, bohužel pouze někteří z nich v něm umí dobře programovat. Dopředu proto říkám: toto není učebnice programovacího jazyka, toto je učebnice programování. Mým cílem není naučit vás všem finesám a zákoutím použitého programovacího jazyka, ale naučit vás především efektivně navrhovat a vytvářet spolehlivé a snadno udržovatelné programy. Jinými slovy: chci vás naučit dovednostem, které budete moci použít, i když budete programovat v jiném programovacím jazyce (a k tomu určitě dojde – já sám jsem jich byl nucen vystřídat za svoji praxi 24). Jazyky přicházejí a odcházejí. Základní programátorské @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 13 z 433 14 Myslíme objektově v jazyku Java 1.5 techniky a způsob myšlení však žijí daleko déle než jazyky, s kterými byly zavedeny. Výuce programovacího jazyka se však nevyhneme. Budete-li si chtít vyzkoušet to, co jste se naučili, nezbude vám, než program v nějakém programovacím jazyce vytvořit. Pro demonstraci látky vysvětlované v této učebnici budu používat programovací jazyk Java. Zvolil jsem jej z několika důvodů: v současné době je to nejrozšířenější programovací jazyk, je to moderní programovací jazyk, na němž lze demonstrovat použití všech důležitých postupů, programovací jazyk i vývojové nástroje je možné získat zdarma, vytvořené programy nejsou omezeny ne jediný operační systém, ale chodí prakticky všude, je k němu k dispozici vývojový nástroj specializovaný pro výuku, který je dokonce lokalizovaný do češtiny. Jak jsem již řekl, v této učebnici se chci soustředit spíše na to, jak programovat, a programovací jazyk používám pouze jako prostředek k tomu, abychom si mohli vysvětlené věci hned také vyzkoušet. Zkoušet ale budeme hodně, takže se v průběhu výuky naučíte nejenom programovat, ale zároveň získáte potřebnou praxi při řešení nejrůznějších úloh v programovacím jazyku Java. Nebudeme spolu řešit pouze jednoduché úlohy, jejichž hlavním účelem je demonstrovat vysvětlovanou vlastnost jazyka (i když se jim nebudu vyhýbat), ale budu se vám naopak snažit předkládat i úlohy složitější, a to i za cenu toho, že část úlohy, která bude používat doposud nevysvětlené konstrukce, za vás budu muset předem vyřešit sám. Takovéto úlohy daleko lépe odpovídají těm, s nimiž se budete v praxi setkávat. Typickou úlohou programátora totiž není navrhnout kompletní řešení nějakého jednoduchého problému, ale naopak doplnit stávající, většinou velmi složitý a někým jiným napsaný program, o nějakou novou funkci. Při práci na takovýchto příkladech si vyzkoušíte další potřebnou dovednost, kterou je schopnost orientovat se v programu, který je mnohem složitější, než byste sami dokázali v daném okamžiku naprogramovat. Tato kniha se od ostatních učebnic programování liší ještě v jedné věci: většina ostatních učebnice sice na začátku vysvětlí, co je to objektové programování, ale pak na něj na chvíli zapomenou a začnou výkladem klasických programovacích konstrukcí. My to uděláme právě obráceně. Jestli se vám má dostat objektově orientované myšlení pod kůži, musíme s jeho výkladem začít hned a nezatěžovat vás napřed klasickými konstrukcemi, které by vaše myšlení směřovaly trochu jinam, takže byste se museli po chvíli zase přeorientovávat. Na klasické konstrukce @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 14 z 433 Úvod 15 samozřejmě nezapomeneme, ale dojde na ně řada až v době, kdy již budete mít za sebou několik objektově orientovaných programů, které budete moci pomocí těchto konstrukcí dále vylepšovat. Kniha je rozdělena do čtyř částí. V první části se seznámíte s třídami a objekty a naučíte se pracovat s vývojovým prostředím. Druhá část před vámi odkryje klíčová zákoutí objektově orientovaného programování a naučí vás přemýšlet při tvorbě programů objektově. Třetí část pak doplní vaše znalosti o klasické programové konstrukce a ukáže vám, jak se řeší složitější úlohy z praktického života. Celou knihou se potáhne jedna úloha, která musí být dostatečně jednoduchá, abyste na ní vykládanou látku co nejlépe pochopili. Touto úlohou bude práce s grafickými tvary zobrazovanými na simulovaném plátně. Při práci s grafickými tvary si můžeme postupně vysvětlit i ty nejsložitější programátorské obraty způsobem, který je dostatečně průzračný a pochopitelný. Abyste nepodlehli dojmu, že programování spočívá pouze v kreslení grafických tvarů, budou pro vás paralelně k této úloze připraveny další, většinou složitější (a doufám i zajímavější) úlohy, na kterých si budete moci vyzkoušet, jak jste vykládanou látku pochopili. Úlohy v první části knihy budou, pravda, ještě triviální, protože toho ještě nebudete moc umět. Druhá část už přinese zajímavější úlohy s nejrůznějšími animacemi. Ve třetí části se pak rozmáchneme a začneme řešit úlohy, z nichž některé se mohou stát základem vašich budoucích programů. Žádná z úloh zadaných v této knize není pouze zadána, všechny úlohy jsou vyřešeny, abyste si mohli porovnat své řešení se vzorovým a abyste se v případě, kdy se dostanete do těžkostí a nebudete si vědět rady, měli kam obrátit pro inspiraci. Budete-li se však chtít opravdu naučit programovat, doporučuji vám vyřešit všechny tyto doplňkové úlohy vlastní hlavou. Na závěr tohoto úvodu dovolte ještě jednu omluvu. Vím, že kniha je výrazně tlustší, než začátečnické učebnice obvykle bývají. Není to proto, že bych toho v ní chtěl vysvětlit tak moc, je to spíš proto, že se snažím všechna krizová místa spolu s vámi projít krok za krokem. Nechtěl jsem jen naznačovat, na co si máte dát pozor, ale u většiny častých chyb jsem také uvedl příklad, v němž se chyba nebo nepříjemná situace vyskytuje, a zároveň ukázal řešení nebo potřebnou reakci. Protože je ale takových míst hodně, kniha nám trochu narostla. Doufám, že i vy budete tyto podrobné vysvětlivky považovat za užitečné. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 15 z 433 16 Myslíme objektově v jazyku Java 1.5 Použité konvence K tomu, abyste se v textu lépe vyznali a také abyste si vykládanou látku lépe zapamatovali, používám několik prostředků pro odlišení a zvýraznění textu. Objekty Názvy objektů v programu a další texty, které chci zvýraznit, vysazuji tučně. Názvy Názvy firem a jejích produkt vysazuji kurzivou. Kurzivou vysazuji také názvy kapitol, podkapitol a oddílů, na které se v textu odkazuji. Citace Texty, které si můžete přečíst na displeji, např. názvy polí v dialogových oknech či názvy příkazů v nabídkách, vysazuji tučným bezpatkovým písmem. Adresy Názvy souborů a internetové adresy vysazuji obyčejným bezpatkovým písmem. Program Texty programů a jejich částí vysazuji neproporcionálním písmem.. Kromě částí textu, které považuji za důležité zvýraznit nebo alespoň odlišit od okolního textu, najdete v textu ještě řadu doplňujících poznámek a vysvětlivek. Všechny budou v jednotném rámečku, který bude označen ikonou charakterizující druh informace, kterou vám chce poznámka či vysvětlivka předat. ☯ Symbol jin-jang bude uvozovat poznámky, s nimiž se setkáte na počátku každé kapitoly a ve kterých si povíme, co se v dané kapitole naučíme. Otevřená schránka s dopisy označuje poznámku oznamující název předpřipraveného projektu, s nímž budeme v dalším textu pracovat a případně některé další podrobnosti. Všechny tyto projekty jsou stručně popsané v příloze Použité projekty na straně 417 a naleznete je na adrese http://vyuka.pecinovsky.cz, odkud si je můžete stáhnout. Určitě byste je měli mít stažené a připravené před tím, než začnete knížku doopravdy studovat. Obrázek knihy označuje poznámku týkající se používané terminologie. Tato poznámka většinou upozorňuje na další používané termíny označující stejnou skutečnost. Seznam všech terminologických poznámek najdete v rejstříku pod heslem „terminologie“. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 16 z 433 Úvod 17 Obrázek počítače označuje zadání úkolu, který máte samostatně vypracovat.Seznam všech úloh najdete v rejstříku pod heslem „úloha“. Píšící ruka označuje obyčejnou poznámku, ve které pouze doplňuji informace z hlavního proudu výkladu o nějakou zajímavost. Ruka s hrozícím prstem upozorňuje na věci, které byste měli určitě vědět a na které byste si měli dát pozor, protože jejich zanedbání vás většinou dostane do problémů. ☺ Usměváček vás bude upozorňovat na různé tipy, kterými můžete vylepšit svůj program nebo zefektivnit svoji práci. Mračoun vás naopak bude upozorňovat na různá úskalí programovacího jazyka nebo programů, s nimiž budeme pracovat, a bude vám radit, jak se těmto nástrahám vyhnout či jak to zařídit, aby vám alespoň pokud možno nevadily. Brýle označují tzv. „poznámky pro šťouraly“, ve kterých se vás snažím seznámit s některými zajímavými vlastnostmi probírané konstrukce nebo upozorňuji na některé souvislosti, avšak které nejsou k pochopení látky nezbytné. Symbol znamení raka označuje poznámku, ve které poukazuji na interpretaci nějakého obratu či konstrukce v analogii ze světa robotů, kterou zavádím v kapitole Analogie na straně 43. Seznam všech odkazů na tuto analogii najdete v rejstříku pod heslem „analogie“. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 17 z 433 Část 1: Zapouzdření @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 18 z 433 Kapitola 1: Seznamujeme se s nástroji 1. 19 Seznamujeme se s nástroji Kapitola 1 Seznamujeme se s nástroji ☯ 1.1 Co se v kapitole naučíme V této kapitole se seznámíte s nástroji, které budete při studiu dalších částí knihy potřebovat. Nejprve vám povím o historii a současných trendech v programování a prozradím vám, proč jsem pro výuku vybral programovací jazyk Java. Pak vám ukážu vývojové prostředí BlueJ a naznačím, jak je máte nainstalovat na svůj počítač. Poté vám vysvětlím, jak jsou organizovány doprovodné projekty k učebnici a ukážu vám, jak je připravit, abyste s nimi mohli v průběhu studia učebnice snadno pracovat. Na závěr vám předvedu, jak otevřít projekt ve vývojovém prostředí BlueJ, prozradím vám, co je to diagram tříd a naučím vás, jak je možno tento diagram v prostředí BlueJ upravovat. Trochu historie První počítače Historie počítačů (tj. strojů, u nichž je postup výpočtu řízen programem) a programování se začala psát již v devatenáctém století. Charles Babbage tehdy dostal zakázku od anglického námořnictva na vylepšení svého počítacího stroje pro výpočet navigačních tabulek. V roce 1848 dokončil návrh počítacího stroje, který byl řízený programem zadávaným na děrných štítcích. Zbytek života se pak věnoval jeho konstrukci. Stroj byl mechanický a měl být poháněn parním strojem, ale z programátorského hlediska již umožňoval značnou část operací, kterými se honosí současné počítače. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 19 z 433 20 Myslíme objektově v jazyku Java 1.5 Zajímavé je, že prvním programátorem byla žena – Ada Lovelance Augusta (mimochodem dcera známého romantického básníka lorda Byrona), která se s Babbagem spřátelila a pomáhala mu s některými výpočty. Jednou se na výletě v Itálii setkala s italským poručíkem, který pro místní smetánku přednášel o Babbageově stroji. Aby Babbageův stroj zpopularizovala, přeložila přednášku, která popisovala hardware tohoto počítače, do angličtiny a na Babbageův popud překlad doplnila „poznámkami překladatele“, kde vysvětlila možnosti stroje ze softwarového hlediska. Svůj výklad doplnila i krátkým programem na výpočet Fibonacciho čísel – prvním publikovaným počítačovým programem na světě. (Na její počest byl v osmdesátých letech minulého století pojmenován programovací jazyk Ada.) Babbageovi se jeho počítač nepodařilo rozchodit, protože vyžadoval součástky z materiálu s pevností pancéřové oceli opracované s hodinářskou přesností, a to bylo pro tehdejší technologii příliš velké sousto. Rozchodil jej až jeho vnuk, který byl americkým generálem, a při odchodu do penze se rozhodl, že všem ukáže, že dědeček nebyl blázen a jeho stroj by byl schopen provozu. První funkční počítač postavil až v roce 1938 v Německu Konrád Zuse. Další počítače se objevily v průběhu druhé světové války. Nejslavnějším z nich byl ENIAC, který byl vyroben v roce 1944 a byl prvním čistě elektronickým počítačem (ostatní ještě používaly relé1 či dokonce mechanické prvky). Skutečné počítačové (a tím i programátorské) orgie však začaly až v padesátých letech, kdy se začaly počítače sériově vyrábět a počítačová věda (computer science) se zabydlela na všech univerzitách. Program musí být především spolehlivý Počítače byly postupně nasazovány v dalších a dalších oblastech a programátoři pro ně vytvářeli dokonalejší a dokonalejší programy. Programy byly čím dál rafinovanější a složitější a to začalo vyvolávat velké problémy. Programátoři totiž přestávali být schopni své programy rozchodit a když je vítězně rozchodili, nedokázali z nich v rozumném čase odstranit chyby, které uživatelé v programu objevili. Tato krize vedla postupně k zavádění nejrůznějších metodik, které měly jediný cíl: pomoci programátorům psát spolehlivé a snadno upravovatelné programy. V padesátých letech minulého století se tak prosadily vyšší programovací jazyky, v šedesátých letech modulové programování, v sedmdesátých letech na ně navá- 1 Relé je elektromechanický prvek, kde průchod proudu cívkou zapříčiní sepnutí nebo rozpojení kontaktů. Rychlá relé dokázala sepnout i 100krát za sekundu – to byla také maximální rychlost tehdejších počítačů. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 20 z 433 Kapitola 1: Seznamujeme se s nástroji 21 zalo strukturované programování a v průběhu osmdesátých a zejména pak devadesátých let ovládlo programátorský svět objektově orientované programování, jehož vláda pokračuje dodnes. Hlavním cílem programátorů v počátcích programování bylo, aby jejich programy spotřebovaly co nejméně paměti a byly co nejrychlejší. Tehdejší počítače totiž měly paměti málo, byly velice pomalé a jejich strojový čas byl drahý. Se stoupající složitostí programů však byly takto psané programy stále méně stabilní a stále hůře udržovatelné. Současně s tím, jak klesala cena počítačů, jejich strojového času i paměti, začínal být nejdražším článkem v celém vývoji člověk. Cena strojového času a dalších prostředků spotřebovaných za dobu života programu začínala být pouze zlomkem ceny, kterou bylo nutno zaplatit za jeho návrh, zakódování, odladění a následnou údržbu. Začal být proto kladen stále větší důraz na produktivitu programátorů i za cenu snížení efektivity výsledného programu. Prakticky každý program zaznamená během svého života řadu změn. Tak, jak se průběžně mění požadavky zákazníka na to, co má program umět, je program postupně upravován, rozšiřován a vylepšován. Celé současné programování je proto vedeno snahou psát programy nejenom tak, aby pracovaly efektivně, tj. rychle a s minimální spotřebou různých zdrojů (operační paměť, prostor na disku, kapacita sítě atd.), ale aby je také bylo možno kdykoliv jednoduše upravit a vylepšit. Předchozí zásady krásně shrnul Martin Fowler ve své knize Refactoring: „Napsat program, kterému porozumí počítač, umí i hlupák. Dobrý programátor píše programy, kterým porozumí i člověk.“ Mějte při tvorbě svých programů tuto větu neustále na paměti. 1.2 Objektově orientované programování – OOP Vývoj metodik programování Jak jsem řekl, v průběhu doby se prosadilo několik metodik, které doporučovaly, jak programovat, abychom byli s programem co nejdříve hotovi a výsledný program byl co nejkvalitnější. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 21 z 433 22 Myslíme objektově v jazyku Java 1.5 Modulární programování ukazovalo, že rychlost vývoje i kvalitu výsledné programu zvýšíme, když vhodně rozdělíme velký projekt do sady menších, rozumně samostatných modulů. Strukturované programování se ponořilo do hloubky kódu a ukazovalo, že dalšího zvýšení produktivity vývoje i kvality výsledných programů dosáhneme dodržením několika jednoduchých zásad při vlastním psaní kódu. Objektově orientované programování (v dalším textu budu využívat zkratku OOP) se naopak obrátilo do vyšších hladin a ukázalo, že vhodným zvýšením abstrakce dokážeme rychle a spolehlivě navrhovat a vyvíjet ještě větší a komplikovanější projekty. Jednou z největších překážek v efektivní tvorbě kvalitních programů je tzv. sémantická mezera mezi tím, co chceme vytvořit a tím, co máme k dispozici. Naše programy mají řešit široké spektrum úkolů od řízení mikrovlnné trouby přes nejrůznější kancelářské a grafické programy a hry až po složité vědecké úlohy, kosmické lety či protivzdušnou obranu kontinentu. Ve svém rozletu jsme ale odkázáni na stroje, které si umějí pouze hrát s nulami a jedničkami. Čím budou naše vyjadřovací možnosti blíže zpracovávané skutečnosti, tím rychleji a lépe dokážeme naše programy navrhnout, zprovoznit a udržovat. Jinými slovy: Kvalita programu a rychlost jeho tvorby je velice úzce svázána s hladinou abstrakce, kterou při jejich tvorbě používáme. Budeme-li např. programovat ovládání robota, bude pro nás výhodnější programovací jazyk, v němž můžeme zadat příkazy typu „zvedni pravou ruku“, než jazyk, v němž musíme vše vyjadřovat pomocí strojových instrukcí typu „dej do registru A dvojku a obsah registru A pak pošli na port 27“. OOP přichází s výrazovými prostředky, které nám umožňují maximálně zvýšit hladinu abstrakce, na které se „bavíme“ s našimi programy a tím maximálně zmenšit onu sémantickou mezeru mezi tím, co máme k dispozici a co bychom potřebovali. Principy OOP Objektově orientované programování vychází z myšlenky, že všechny programy, které vytváříme, jsou simulací buď skutečného nebo nějakého námi vymyšleného virtuálního světa. Čím bude tato simulace přesnější, tím bude výsledný program lepší. Všechny tyto simulované světy jsou ve skutečnosti světy objektů, které mají různé vlastnosti a schopnosti a které spolu nějakým způsobem komunikují. Komunikací se přitom nemyslí jen klasická komunikace mezi lidmi či zvířaty, ale i mnohem obecnější vzájemné interakce (např. židle a podlaha spolu komunikují @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 22 z 433 Kapitola 1: Seznamujeme se s nástroji 23 tak, že židle stojí na podlaze a naopak podlaha podpírá židli, aby se skrz ní neprobořila). Budeme-li chtít, aby naše programy modelovaly tento svět co nejvěrněji, měly by být schopny modelovat obecné objekty spolu s jejich specifickými vlastnostmi a schopnostmi a současně modelovat i jejich vzájemnou komunikaci. Máme-li být schopni rychle vytvářet kvalitní programy, měli bychom mít k dispozici jazyk, jehož jazykové konstrukce nám umožní se vyjadřovat co nejpřirozeněji a co nejméně se nechat znásilňovat omezeními počítače, na kterém má program běžet. Musí umožnit co nejvyšší míru abstrakce, při níž se můžeme vyjadřovat tak, abychom mohli přirozeně popsat modelovanou skutečnost. Všechny moderní programovací jazyky se honosí přídomkem „objektově orientované“. Tím se nám snaží naznačit, že nabízejí konstrukce, které umožňují rozumně modelovat náš okolní, objekty tvořený svět (a nejen jej). 1.3 Překladače, interprety, platformy Tato podkapitola je určena těm, kteří se nespokojí jen s tím, že věci fungují, ale chtějí také vědět, jak fungují. Naznačíme si v ní, jak je to v počítači zařízeno, že programy pracují. Operační systém a platforma Operační systém je podle definice sada programů, jejímž úkolem je zařídit, aby počítač co nejlépe sloužil zadanému účelu. Operační systémy osobních počítačů se snaží poskytnout co největší komfort a funkčnost jak lidským uživatelům, tak programům, které operační systém nebo tito uživatelé spouští. (Teď nehodnotím, jak se jim to daří.) Operační systém se snaží uživatele odstínit od hardwaru použitého počítače. Uživatel může střídat počítače, avšak dokud bude na všech stejný operační systém, bude si se všemi stejně rozumět. Při obsluze lidského uživatele to má operační systém jednoduché: člověk komunikuje s počítačem pomocí klávesnice, obrazovky, myši a případně několika dalších zařízení. Ty všechny může operační systém převzít do své správy a zabezpečit, aby se nejrůznější počítače chovaly vůči uživateli stejně. U programů to má ale složitější. Programy totiž potřebují komunikovat nejenom s operačním systémem (např. když chtějí něco přečíst z disku nebo na něj zapsat), ale také přímo s procesorem, kterému potřebují předat své instrukce k vykonání. Problémem ale je, že různé procesory rozumí různým sadám instrukcí. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 23 z 433 24 Myslíme objektově v jazyku Java 1.5 Abychom věděli, že náš program na počítači správně poběží, musíme vědět, že počítač bude rozumět té správné sadě instrukcí a že na něm poběží ten správný operační systém. Kombinaci operační systém + procesor budeme v dalším textu označovat jako platforma. Nejrozšířenější platformou současnosti je operační systém Windows na počítačích s procesory kompatibilními s procesorem Intel Pentium. Další vám známou platformou bude nejspíš operační systém Linux na počítačích s procesory kompatibilními s Pentiem. Oba zmíněné operační systémy však mají své verze běžící na počítačích s jinými procesory. Programovací jazyky Jak asi všichni víte, pro zápis programů používáme nejrůznější programovací jazyky. Ty jsou vymýšleny tak, aby v nich mohl člověk co nejlépe popsat svoji představu o tom, jak má počítač splnit požadovanou úlohu. Program zapsaný v programovacím jazyku pak musíme nějakým způsobem převést do podoby, které porozumí počítač. Podle způsobu, jakým postupujme, dělíme programy na překládané a interpretované. U překládaných programů se musí napsaný program nejprve předat zvláštním programu nazývanému překladač (někdo dává přednost termínu kompilátor), který jej přeloží (zkompiluje), tj. převede jej do podoby, s níž si již daná platforma ví rady, tj. musí jej přeložit do kódu příslušného procesoru a používat instrukce, kterým rozumí použitý operační systém. Přeložený program pak můžeme kdykoliv na požádání spustit. Naproti tomu interpretovaný program předáváme v podobě, v jaké jej programátor vytvořil, programu označovanému jako interpret. Ten obdržený program prochází a ihned jej také provádí. Výhodou překládaných programů je, že většinou běží výrazně rychleji, protože u interpretovaných programů musí interpret vždy nejprve přečíst kus programu, zjistit, co má udělat a teprve pak může tento požadavek vykonat. Výhodou interpretovaných programů bývá na druhou stranu to, že jim většinou tak moc nezáleží na tom, na jaké platformě běží. Stačí, když na daném počítači běží potřebný interpret. Naproti tomu překládané programy se většinou musí pro každou platformu trochu (nebo také hodně) upravit a znovu přeložit. Vedle těchto základních druhů programů existují ještě hybridní programy, které jsou současně překládané i interpretované a které se snaží sloučit výhody obou skupin. Hybridní program se nejprve přeloží do jakéhosi mezijazyka, který je vymyšlen tak, aby jej bylo možno co nejrychleji interpretovat. Takto přeložený @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 24 z 433 Kapitola 1: Seznamujeme se s nástroji 25 program je potom interpretován speciálním interpretem označovaným často jako virtuální stroj1. Chytřejší verze těchto interpretů (virtuálních strojů) dokonce dokáží odhalit často se opakující části kódu a někam stranou je přeložit, aby je nemusely pořád kolem dokola interpretovat. Program pak může běžet skoro stejně rychle jako překládaný (a někdy i rychleji). Hybridní programy spojují výhody obou kategorií. K tomu, aby v nich napsané programy mohly běžet na různých platformách, stačí pro každou platformu vyvinout potřebný interpret – virtuální stroj. Je-li pak tento virtuální stroj dostatečně „chytrý“ (a to jsou v současné době prakticky všechny), běží program téměř stejně rychle, jako kdyby byl přeložený. Na překládané, interpretované a hybridní bychom měli dělit programy, avšak často se takto dělí i programovací jazyky. Je sice pravda, že to, zda bude program překládaný, interpretovaný nebo hybridní není závislé na použitém jazyku, ale je to především záležitostí implementace daného jazyka, nicméně každý z jazyků má svoji typickou implementaci, podle které je pak zařazován. Prakticky všechny jazyky sice mohou být implementovány všemi třemi způsoby a řada jich opravdu ve všech třech podobách existuje (jako příklad bychom mohli uvést jazyky Basic, Java nebo Pascal), ale u většiny převažuje typická implementace natolik výrazně, že se o těch ostatních prakticky nemluví. Z vyjmenované trojice je např. klasický Basic považován za interpretovaný jazyk, Java za hybridní a Pascal za překládaný. Hybridní implementace jazyků se v posledních letech výrazně prosadily a jsou dnes králi programátorského světa. Vyvíjí v nich převážná většina programátorů a procento implementací v těchto jazycích neustále vzrůstá. 1.4 Java a její zvláštnosti O splnění zásad objektově orientovaného programování se snaží tzv. objektově orientované jazyky. Mezi nimi je v současné době nejpopulárnější programovací jazyk Java, který se narodil v roce 1995. Hned po svém vzniku zaznamenal velký 1 Virtuální stroj se mu říká proto, že se vůči programu v mezijazyku chová obdobně, jako se chová procesor vůči programu v čistém strojovém kódu. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 25 z 433 26 Myslíme objektově v jazyku Java 1.5 ohlas a během několika let v něm začalo vyvíjet své programy více než 3 milióny programátorů a toto číslo stále stoupá. V roce 2003 se podle průzkumů renomovaných společností stal nejpoužívanějším programovacím jazykem. Na převážné většině univerzit se Java používá jako vstupní jazyk pro výuku programování. Jaké jsou vlastnosti tohoto programovacího jazyka, že v tak krátké chvíli tak významně ovlivnil programátorský svět? Důvodů je celá řada. Zde uvedu jen ty z nich, které se výhodně projeví při výuce programování. Objektově orientovaná Java plně podporuje objektově orientované programování. Na rozdíl od C++ a dalších jazyků „klasického ražení“ již neumožňuje napsat program, který by nebyl objektově orientovaný. Její autoři se přitom do ní snažili začlenit převážnou většinu zásad objektově orientovaného programování. Předchozí tvrzení musím trochu zlehčit. Platí obecná zásada, že jako čuně mohu programovat v jakémkoliv programovacím jazyce. Java (a s ní i dalších moderní programovací jazyky) nedovolují zapsat program, který by nebyl alespoň formálně objektově orientovaný. Bude-li objektově orientovaný doopravdy, tj. i svou architekturou a duchem, to už záleží na programátorovi. To za vás žádný programovací jazyk neudělá. Jednoduchá Java je velice jednoduchý jazyk. Základy jeho syntaxe může člověk, který umí programovat, zvládnout během několika hodin. Pak už se může soustředit na poznání a pochopení funkce klíčových knihoven a na jejich co nejefektivnější využití. Jednoduchost jazyka však neznamená jeho omezenost. Jak jsem již řekl, Java je v současnosti nejpoužívanějším programovacím jazykem. Programují se v ní aplikace všech rozměrů od drobných programů, které se umísťují na čipové karty, přes programy pro mobilní telefony, desktopové aplikace až po obří projekty rozprostřené na řadě vzájemně komunikujících počítačů. Překládaná i interpretovaná Java je současně překládaná i interpretovaná (řadí se proto mezi hybridní programovací jazyky). Programy v jazyku Java se nejprve přeloží do speciálního tvaru nazývaného bajtkód, který pak analyzuje a interpretuje speciální program nazývaný virtuální stroj Javy (Java Virtual Machine – používá se pro něj zkratka @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 26 z 433 Kapitola 1: Seznamujeme se s nástroji 27 VM). Úkolem virtuálního stroje je nejenom vykonat příslušný program, ale také tento program „odstínit“ od hardwaru a operačního systému, na kterém program běží. Cílem překladu do bajtkódu je převést program do podoby, s níž se bude virtuálnímu stroji co nejlépe pracovat. Současné virtuální stroje jsou dokonce natolik inteligentní, že umí provádění programu za běhu optimalizovat, takže program na nich běží skoro tak rychle, jako kdyby byl přeložený, a v některých speciálních případech dokonce rychleji. Multiplatformní Jedním z klíčových záměrů autorů Javy bylo vytvořit nejenom jazyk, ale celou platformu (= prostředí, ve kterém běhají programy). Tuto platformu realizuje virtuální stroj spolu se základní knihovnou nejpoužívanějších funkcí. Virtuální stroj umožňuje, aby jeden a týž program běhal na různých počítačích a operačních systémech. Pro každou konfiguraci hardwaru a operačního systému lze definovat její vlastní virtuální stroj. Ten se pak stará o správný běh programů, takže se program (a s ním i uživatel) vůbec nemusí starat o to, na jakém hardwaru a operačním systému zrovna běží. Java běhá pod systémy Windows, Unix, Linux, MacOS, Solaris a řadou dalších operačních systémů. Vytvoříte-li program v Javě, můžete jej spustit téměř na libovolném počítači. Jak mnozí z vás ví, programy v Javě je možné spouštět i na řadě mobilních telefonů a dokonce ovládají i miliony čipových karet. Java je totiž nejenom programovací jazyk, ale také platforma. Tímto termínem označujeme soubor programů, které se vůči programům běžícím na dané platformě chovají obdobně jako operační systém – odstiňují je od detailů použitého hardwaru a operačního systému. Kdykoliv tyto programy po systému něco chtějí, požádají o to platformu a ta jim příslušné služby zprostředkuje. Takovým programům pak může být jedno, pod jakým operačním systémem běží, protože se beztak „baví“ pouze s platformou. Java nabízí dvě verze platformy. Jednodušší JRE (Java Runtime Environment – běhové prostředí Javy) poskytuje vše potřebné pro běh programů. Komplexnější SDK (Software Development Kit – sada nástrojů pro vývoj softwaru), označovaná někdy také JDK (Java Development Kit) je vlastně JRE doplněné o základní vývojové nástroje (překladač, generátor dokumentace, ladící program a další) a poskytuje tak vše potřebné pro vývoj programů. Vy se chcete naučit pomocí této učebnice programovat, tak budete potřebovat mít instalované SDK. Uživatelům, kteří pak budou spouštět vaše programy, stačí JRE. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 27 z 433 28 Myslíme objektově v jazyku Java 1.5 Obě verze si můžete stáhnout a instalovat zdarma. Postup instalace tohoto prostředí je vysvětlen v příloze Instalace vývojového prostředí na straně 407. V dalším textu budu předpokládat, že máte SDK nainstalováno. 1.5 Vývojové prostředí BlueJ Mnozí autoři učebnic vystačí při výkladu s nástroji z SDK doplněnými o nějaký editor, ve kterém píší zdrojové texty programů. Obdobným způsobem pracuje i část programátorů (podle některých průzkumů takto pracuje asi 7 % programátorů). Většina programátorů však dává přednost speciálním vývojovým prostředím označovaným souhrnně zkratkou IDE – Integrated Development Environment (integrované vývojové prostředí), což je sada programů, která nabízí řadu funkcí, jež vývoj programů výrazně zefektivňují. IDE dáme přednost i my. V této učebnici budu používat vývojové prostředí BlueJ (čtěte blúdžej), které bylo vyvinuté speciálně pro výuku objektově orientovaného programování a oproti jiným IDE nabízí několik vlastností, jež se nám budou při výkladu základů OOP hodit. Je maximálně jednoduché, takže se začátečníci mohou soustředit na své programy a nejsou rozptylováni záplavou možností, kterými je rozptylují profesionální vývojová prostředí a které na počátku beztak neumí využít. Je názorné, protože slučuje možnosti klasického textového zápisu programů s možností definice jeho architektury v grafickém prostředí. Tato koncepce se stává základním postupem návrhu programů. BlueJ je prozatím jediné prostředí, které je k dispozici zdarma a přitom je schopno vytvořit na základě grafického návrhu kostru programu a průběžně zanášet změny v programu do jeho grafické podoby a naopak změny v grafickém návrhu do textové podoby programu, aniž by ovlivnilo ty části programu, které programátor zadal „ručně“. Je interaktivní – umožňuje přímou práci s objekty. Ostatní prostředí vám většinou povolují pouze vytvořit program, který můžete spustit. Přímou komunikaci s jednotlivými součástmi programu, tj. vytváření objektů a zasílání zpráv řízené interaktivně uživatelem, však většinou neumožňují. Toto prostředí se používá ve vstupních kurzech programování na stovkách univerzit a školících středisek po celém světě (v létě 2004 se k používání tohoto prostředí hlásilo okolo 400 univerzit a školících center). Jak se můžete přesvědčit na stránkách http://www.BlueJ.org, jeho výhodné vlastnosti přivedly řadu programá@Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 28 z 433 Kapitola 1: Seznamujeme se s nástroji 29 torů k tomu, že je začali používat i pro vývoj menších profesionálních aplikací. Tito programátoři oceňují zejména jeho malou paměťovou náročnost (BlueJ vystačí se 48 MB RAM, zatímco běžná profesionální vývojová prostředí požadují minimálně 256 MB RAM) a originální, jinde nenabízené možnosti interaktivního ladění programů. Stejně jako SDK, i vývojové prostředí BlueJ si můžete stáhnout a instalovat zdarma. Vše potřebné pro jeho instalaci se dozvíte v příloze Instalace prostředí BlueJ na straně 409. V dalším textu budu předpokládat, že máte toto prostředí nainstalováno spolu s rozšířením, o němž se v této kapitole zmiňuji. 1.6 Projekty a BlueJ V současných vývojových prostředích již nepracujeme s programy, ale s projekty. Projekt může obsahovat jeden program (tj. něco, co spustíme a ono to běží) nebo několik programů – to podle toho, jak se nám to zrovna hodí. Typickým příkladem jednoduchého projektu sestávajícího z několika programů byl vítězný projekt mladší kategorie z programátorské soutěže BB20021. Byl jím soubor deskových her, který obsahoval 5 programů: hry Dáma a Reversi vytvořené v programovacím jazyku Baltík, hru Piškvorky vytvořenou v jazyku Baltazar, hlavní program umožňující volbu hry vytvořený opět v jazyku Baltík a skript umožňující přepínání programů vytvořený v jazyku JavaScript. Jednotlivé hry byly v případě potřeby spustitelné samostatně. Jejich sloučením do většího celku a doplněním o nadstavbové uživatelské rozhraní pro výběr hry vzniklo takové malé herní centrum, které mělo pro řadu uživatelů větší užitnou hodnotu, než sada samostatných, na sobě nezávislých her. Umístění projektů na disku Doporučuji vám, abyste si pro projekty zřídili novou složku. Do ní pak budete umisťovat jak svoje projekty, tak projekty, které budou součástí tohoto seriálu. Jednou z možností je zřídit na datovém disku složku Java s podsložkami Texty a Projekty. Do složky Texty si můžete vložit texty týkající se programování a Javy, ve 1 Program si můžete stáhnout na stránkách www.sgp.cz. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 29 z 433 30 Myslíme objektově v jazyku Java 1.5 složce Projekty si pak zřiďte pro každý projekt novou složku, ve které budou umístěny všechny soubory, které k němu budou patřit. Jakmile složku pro své projekty zřídíte, můžete do ní hned stáhnout projekty k této učebnici, které najdete na adrese http://vyuka.pecinovsky.cz a které jsou stručně popsány v příloze Použité projekty na straně 417. Windows a substituované disky V operačním systému Windows můžete používat 26 logických disků – pro každé písmeno abecedy jeden. Většina uživatelů však používá pouze zlomek tohoto počtu. Disky A: a B: bývaly donedávna vyhrazeny pro mechaniky pružných disků, na disku C: má většina uživatelů operační systém. Pak na počítači nejdete ještě pár dalších jednotek vyhrazených pro další oddíly velkého disku, pro CD-ROM a případně ještě nějaké síťové disky či zařízení, která se vůči počítači tváří jako další disk (např. paměť Flash-RAM) a tím výčet končí. Průměrný uživatel tak má většinu písmen abecedy nevyužitých. Operační systém umožňuje použít tyto názvy pro tzv. substituované disky, což jsou složky, které se rozhodnete vydávat za logický disk. Protože o této možnosti většina uživatelů neví a přitom je to funkce velice užitečná, ukážu vám, jak ji můžete využít. Substituované disky se definují pomocí příkazu SUBST název_disku substituovaná_složka Nejjednodušší způsob, jak definovat ve Windows např. substituovaný disk J:, je vložit do složky, kterou budete chtít substituovat jako disk J:, dávkový soubor s příkazem k substituci. (Písmeno J se pro Javu hodí nejlépe, ale můžete si vybrat jakékoliv jiné.) Pokud jste ještě nepracovali s dávkovými soubory, tak vězte, že to jsou obyčejné textové soubory, do nichž zapisujete příkazy pro operační systém. Jejich název může být libovolný, ale musí mít příponu bat (zkratka ze slova batch – dávka). V dávkovém souboru budou následujícím příkazy (na velikosti písmen nezáleží): SUBST J: /d SUBST J: . První příkaz má za úkol zrušit případnou doposud nastavenou substituci disku J: (pokud v daném okamžiku takový disk není, systém vypíše chybovou zprávu, ale jinak se nic nestane), druhý příkaz pak substituuje aktuální složku jako disk J:. Kdykoliv budete od této chvíle chtít substituovat složku, do níž jste dáv@Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 30 z 433 Kapitola 1: Seznamujeme se s nástroji 31 kový soubor umístili, jako zadaný disk, spustíte příslušný dávkový soubor obdobně, jako byste spouštěli aplikaci – tj. např. poklepáním na jeho ikonu v Průzkumníku. Budete-li chtít mít danou substituci nastavenu trvale, můžete umístit zástupce dávkového souboru do startovací nabídky do složky Start → Programy → Po spuštění a systém spustí příslušnou dávku po každém restartu počítače. Po spuštění příslušného dávkového souboru (spouští se poklepáním jako jakýkoliv jiný program nebo skript) se rozšíří používané disky o právě substituovaný disk – v našem případě o disk J:. Kdykoliv od této chvíle budete pracovat s diskem J:, budete ve skutečnosti pracovat s obsahem substituované složky. A naopak: cokoliv uděláte s obsahem substituované složky, uděláte zároveň s obsahem disku J:. Abyste mohli složku substituovat jako nějaký disk, nesmí váš operační systém používat disk označený tímto písmenem pro nějaké hardwarové zařízení (např. pevný disk, CD-ROM, Flash-disk apod.). Písmeno může být použito nejvýše pro jiný substituovaný disk, protože tuto substituci můžete před nastavením nové substituce zrušit (pro tento případ je v dávkovém souboru první příkaz). Substituované složky umožňují sjednotit prostředí několika počítačů. Tuto knihu např. připravuji na několika počítačích, přičemž každý z nich má jinak uspořádané složky (vadí mi to, ale nemohu s tím nic dělat). V každém z nich jsou ale definovány následující substituované disky: J: pro složku s vývojovými nástroji Javy, S: pro složku, ve které je text knihy, V: pro složku, ve které jsou programy pro knihu. Po této úpravě mi již nutnost přecházení mezi různými počítači nevadí, protože vím, že na každém z nich najdu potřebné nástroje a dokumentaci na discích J:, S: a V:. Používáte-li operační systém Windows, můžete urychlit budoucí vyhledání složky s projekty právě tím, že pro ni zřídíte zvláštní substituovaný disk. Kdykoliv se pak obrátíte na příslušný disk, obrátíte se ve skutečnosti k příslušné složce. Pomocí substituce si tak můžete zkrátit cestu k často používaným složkám. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 31 z 433 32 Myslíme objektově v jazyku Java 1.5 Vyhledání a otevření projektu V následujícím textu budeme pracovat s projektem 02_Objekty, který je určen pro první seznámení s prostředím BlueJ, třídami a objekty a s nímž budeme pracovat celou příští kapitolu. Předpokládám, že máte instalováno prostředí BlueJ a stažené doprovodné projekty k učebnici (připomínám, že postup je popsán v příloze Instalace prostředí BlueJ na straně 409). Spusťte BlueJ, zadejte příkaz Projekt → Otevřít. Po zadání příkazu se otevře dialogové okno Otevřít projekt, v němž vyhledáte a označíte složku s projektem a stisknete tlačítko Otevřít (viz obr. 1.1). Obrázek 1.1 Otevření existujícího projektu Na obrázku si všimněte, že ikony projektů (přesněji ikony složek, v nichž je uložen projekt) vypadají jinak než ikony obyčejných složek. BlueJ totiž do složky, ve které budou soubory jeho projektu, přidá svůj vlastní soubor bluej.pkg, do nějž si ukládá informace o grafickém uspořádání objektů v zobrazovaném diagramu. Podle přítomnosti tohoto souboru pak pozná, zda se jedná o složku s jeho projektem nebo o nějakou obyčejnou složku. Najděte složku s projektem 02_Objekty, klepněte na ni a své zadání potvrďte stiskem tlačítka Otevřít. Po otevření projektu by okno BlueJ mělo vypadat obdobně jako na obrázku 1.2. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 32 z 433 Kapitola 1: Seznamujeme se s nástroji 33 Obrázek 1.2 Okno BlueJ po otevření projektu 02_Objekty 1.7 Diagram tříd Velký obdélník zabírající většinu okna projektu obsahuje tzv. diagram tříd našeho projektu. Diagram tříd popisuje strukturu našeho programu a vzájemné vazby mezi jeho částmi. Malé obdélníky v diagramu tříd představují části programu, které nazýváme třídy (za chvíli si o nich budeme povídat podrobněji). Jsou znázorněny podle konvencí grafického jazyka UML1 (vybarvení obdélníků do konvence nepatří, ale zvyšuje přehlednost a názornost diagramu). Čárkované šipky prozrazují, kdo koho používá – např. šipky vedoucí od tříd Obdélník, Trojúhelník a Elipsa ke třídě Plátno naznačují, že tyto třídy třídu Plátno používají (jak se vzápětí dozvíte, tyto obrazce se na plátno kreslí). 1 UML je zkratkou z anglického Unified Modeling Language – sjednocený modelovací jazyk. Je to grafický jazyk, ve kterém programátoři navrhují své aplikace před tím, než začnou psát program. Více se o tomto jazyku můžete dozvědět např. v knize XXX: Myslíme v jazyku UML, Grada, 2002 (ISBN 80-7169-). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 33 z 433 34 Myslíme objektově v jazyku Java 1.5 Bílý obrázek listu papíru v levém horním rohu diagramu představuje textový soubor se základním popisem celého projektu. Když na něj poklepete, otevře se okno editoru, v němž si budete moci tyto informace přečíst a v případě potřeby je i upravit či doplnit. V doprovodných programech k učebnici se snažím důsledně používat české názvy. Jak si zanedlouho prozradíme, Java s nimi nemá problémy. Ty však nastanou v okamžiku, kdy se rozhodneme přenést soubory z jednoho operačního systému na druhý. Operační systémy totiž prozatím nemají sjednocené kódování znaků s diakritikou. To se projevuje zejména v názvech souborů. Název souboru zakódovaný pod jedním operačním systémem nemusí jít pod druhým operačním systémem přečíst. Pro ty, kteří budou mít na svém počítači s diakritikou problémy (a pro ty, kteří s používáním diakritiky zásadně nesouhlasí), je na webu připravena druhá sada, jejíž programy diakritiku nepoužívají. Abyste si mohli navíc převést do „nediakritického“ tvaru i jiné programy, je pro vás na webu připraven program Odhackuj (jeho název je schválně bez diakritiky), který zkopíruje soubory do zadaného adresáře a přitom zbaví jejich názvy i jejich obsah veškeré diakritiky. Tento program je napsaný v Javě, takže by měl chodit na všech počítačích. Šrafování spodní části obdélníků symbolizuje to, že třídy ještě nejsou přeloženy do bajtkódu. To lze snadno napravit: stiskněte tlačítko Přeložit v levé části aplikačního okna. Uvidíte, jak jedna třída za druhou ztmavne (tím prostředí naznačuje, že se překládá), aby pak opět zesvětlela a její šrafování zmizelo (již je přeložena). Od této chvíle můžete třídu používat. Manipulace s třídami v diagramu Polohu jednotlivých tříd v diagramu můžete měnit. Najedete-li ukazatelem myši na obdélník představující danou třídu, změní se podoba kurzoru ze šipky na ukazující ruku. V tomto okamžiku můžete obdélník uchopit (tj. stisknout a podržet primární [většinou levé] tlačítko myši) a přesunout jej do požadované pozice, kde jej upustíte (pustíte tlačítko myši). Po stisku tlačítka myši rám obdélníku ztuční. V průběhu přesunu se s ukazatelem myši bude přesouvat pouze tento rám, takže budete průběžně znát původní i nově nastavovanou pozici obdélníku (viz obrázek 1.3). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 34 z 433 Kapitola 1: Seznamujeme se s nástroji 35 Obrázek 1.3 Posunutí zástupce třídy v diagramu Jsou-li zástupci tříd navzájem spojeni šipkami, bude BlueJ vaše přesuny sledovat a po umístění obrázku třídy do nové pozice šipky vždy příslušně překreslí. Můžete si tak diagram upravit do podoby, v níž vám bude připadat nejpřehlednější a nejnázornější. Obdélníky zastupující třídy můžete nejenom přesouvat, ale můžete měnit i jejich rozměr. Všimněte si, že poté, co na obdélník klepnete, objeví se u pravého dolního rohu ztučnělého rámu dvojité přeškrtnutí. Najedete-li ukazatelem myši do oblasti označené tímto přeškrtnutím, změní se jeho podoba na dvojitou šikmou šipku. Nyní můžete uchopit roh obdélníku, přesunout jej do nové pozice a upravit tak velikost obdélníku (viz obrázek 1.4). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 35 z 433 36 Myslíme objektově v jazyku Java 1.5 Obrázek 1.4 Změna velikosti zástupce třídy v diagramu Někdy se stane, že se nám projekt rozrůstá a rozrůstá a najednou bychom potřebovali posunout více tříd najednou, abychom udělali místo pro třídy, které se chystáme do projektu přidat. Postup je jednoduchý, ale nejprve se musíme naučit vybírat skupiny tříd. Třídy jde zařazovat a vyřazovat ze skupiny vybraných tříd několika způsoby, které můžete kombinovat: Stisknete přeřaďovač CTRL nebo SHIFT (je to jedno) a klepete postupně na obdélníky, které chcete zahrnout od výběru. Najedete ukazatelem myši do volného prostoru a stisknete tlačítko myši. Při stisknutém tlačítku pak popojedete ukazatelem a za ním se roztáhne výběrový podšeděný obdélník. Která třída do něj padne, ta se zařadí mezi vybrané (viz obr. 1.5). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 36 z 433 Kapitola 1: Seznamujeme se s nástroji 37 Obrázek 1.5 Výběr skupiny tříd pomocí „výběrového obdélníku“ Stisknete-li před předchozí operací přeřaďovač CTRL nebo SHIFT, budou se třídy „zasažené výběrovým obdélníkem“ přidávat ke třídám dříve vybraným. Někdy toho vyberete víc, než potřebujete, a potřebujete některé třídy z výběru vyjmout. Pak máte dvě možnosti: Klepnutím myší do volného prostoru zrušíte výběr všech tříd. Klepnutím na třídu při stisknutém přeřaďovači CTRL nebo SHIFT změníte výběr dané třídy – vybranou „odvyberete“ a nevybranou vyberete. Celou operaci přesunu skupiny tříd si můžeme vyzkoušet: 1. Roztáhněte trochu okno projektu, aby bylo kam třídy přesunout. 2. Vyberte třídy tak, jak je naznačeno na obr. 1.5. 3. Najeďte na některou z vybraných tříd tak, aby se ukazatel myši změnil na ukazující ruku (předpokládám standardní nastavení podoby ukazatelů). 4. Uchopte myší tuto třídy (a s ní i ostatní vybrané) a přesuňte je do požadované cílové pozice – viz obr. 1.6. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 37 z 433 38 Myslíme objektově v jazyku Java 1.5 Obrázek 1.6 Přesun bloku tříd 1.8 Shrnutí – co jsme se naučili U programů je nejdůležitější jejich spolehlivost a snadná udržovatelnost. Každý program je jakousi simulací reálného nebo virtuálního světa. Efektivita programování a robustnost vyvinutých programů je silně ovlivněna mírou abstrakce použité při jejich tvorbě. Čím může být naše vyjadřování blíže modelované skutečnosti, tím lépe se nám podaří napsat správný, spolehlivý a robustní program. Moderní, objektově orientované jazyky se snaží zohlednit skutečnost, že jsou modelem světa tvořeného vzájemně interagujícími objekty. Java je moderní programovací jazyk, který je jednoduchý, objektově orientovaný, přenositelný a multiplatformní Při vývoji programů se většinou používají speciální vývojová prostředí označovaná zkratkou IDE. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 38 z 433 Kapitola 1: Seznamujeme se s nástroji 39 V této učebnici budeme používat vývojové prostředí BlueJ. Současná vývojová prostředí organizují vyvíjené programy do tzv. projektů. Strukturu programu můžeme znázornit v diagramu tříd. Při znázorňování struktury programu používáme grafický jazyk UML. Třídy jsou v diagramu tříd znázorněny rozdělenými obdélníky, v jejichž horní části je název třídy. Spodní část je v BlueJ prázdná. Prostředí BlueJ nám umožňuje měnit jejich umístění i velikost obdélníků, představujících v diagramu tříd jednotlivé třídy. Nově zavedené termíny bajtkód BlueJ IDE Interpret Java JDK JRE OOP Projekt Program Překladač SDK UML Vývojové prostředí @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 39 z 433 40 2. Myslíme objektově v jazyku Java 1.5 Pracujeme s třídami a objekty Kapitola 2 Pracujeme s třídami a objekty ☯ 2.1 Co se v kapitole naučíme Tato kapitola předpokládá, že již máte otevřen projekt 02_Objekty, o kterém jsme se bavili v minulé kapitole. Nejprve si povíme, co to vlastně ty třídy a objekty jsou a seznámíme se s novým termínem instance. Pak se s nimi naučíme pracovat v prostředí BlueJ. Vysvětlíme si, jak se objekty vytvářejí, jak reagují na zprávy, co to jsou jejich atributy a že vedle zpráv posílaných instancím existují i zprávy posílané celé třídě. Naučíte se vytvářet instance tříd, posílat jim zprávy a prohlížet si hodnoty jejich atributů. Nejprve trocha teorie Než se vrhneme na vlastní programování, povíme si nejprve trochu o nejdůležitějších termínech, s nimiž se budeme v dalším výkladu setkávat. Abych vás tou teorií příliš neunavoval, tak tyto termíny opravdu jen zavedu a nebudu je nijak rozpitvávat – povím vám pouze to, co budete potřebovat vědět pro porozumění textu této a následující kapitoly. K řadě z nich se pak ještě vrátíme a vysvětlíme si je podrobněji i s ukázkami na příkladech. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 40 z 433 Kapitola 2: Pracujeme s třídami a objekty 41 Třídy a jejich instance Jak jsem již naznačil, výchozím bodem celého objektově orientovaného programování je tvrzení, že svět je světem objektů, které spolu nějak komunikují. Objekty, s nimiž se ve svém okolí setkáváme, můžeme rozdělit do skupin, které mají nějaké společné vlastnosti a které v programování označujeme jako třídy. Vlastní objekty pak označujeme jako instance příslušné třídy – např. židle, na které sedíte, je instancí třídy židlí. Mohli bychom tedy říci, že instance (objekt) je nějakou konkrétní realizací své třídy (židle, na které sedím, je konkrétní realizací obecné židle). Pojem objekt a instance jsou ve skutečnosti synonyma, která můžete s klidným svědomím zaměnit. Termínu objekt se dává většinou přednost tehdy, hovoříme-li o obecných objektech, kdežto termínu instance dáváme přednost v situacích, kdy chceme zdůraznit, do jaké třídy objekty, o nichž hovoříme, patří. Třídy popisují společné vlastnosti svých instancí a definují také nástroje pro jejich vytváření. Přirovnáme-li programování k hraní si na pískovišti, pak třída je něco jako formička a její instance jsou bábovičky, které za pomoci této formičky vytváříme. Při jiném přirovnání bychom mohli prohlásit, že třída je vlastně továrna na objekty – své instance. Třída může mít obecně libovolný počet instancí. Existují však i třídy, které dovolí vytvoření pouze omezeného počtu instancí, někdy dokonce povolí jen jedinou instanci – jedináčka. Nevyskytují se příliš často, ale na druhou stranou nejsou žádnou exotickou výjimkou (s jednou se potkáme hned v prvním projektu). Zprávy Objekty navzájem komunikují. Objektové programování hovoří o tom, že si objekty navzájem posílají zprávy, ve kterých se žádají o různé služby, přičemž onou službou může být často jen informace. Můžeme se např. židle zeptat, je-li čalouněná. V praxi bychom to realizovali nejspíše tak, že bychom k ní poslali upřený pohled, v programu k ní vyšleme zprávu. Na dotaz, co je to program, většinou odpovídám, že je to předpis, jak splnit zadanou úlohu, zapsaný v nějakém programovacím jazyce (dokud není zapsaný v programovacím jazyce, není to program, ale algoritmus). Tato definice je možná přesná, nicméně je tak obecná, že je pro naše účely prakticky nepoužitelná. Defi@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 41 z 433 42 Myslíme objektově v jazyku Java 1.5 nujme si proto, že objektově orientovaný program je v nějakém programovacím jazyce zapsaný popis použitých tříd, objektů a zpráv, které si tyto objekty posílají, doplněný u složitějších programů ještě o popis umístění programů na jednotlivých počítačích a jejich svěření do správy příslušných služebních programů (např. operačních systémů či aplikačních serverů). Budete-li slyšet programátory velkých aplikací hovořit o „deploymentu“, tak vězte, že hovoří o výše zmíněném rozmísťovacím aspektu svých programů. Předchozí definici jsem uváděl proto, aby si ti, kteří mají nějaké zkušenosti s klasickým programováním, uvědomili, že u objektově orientovaného programování vstupují do trochu jiného programátorského vesmíru, než ve kterém se pohybují ti, kteří programují klasicky. Do vesmíru, v němž sice budou nadále používat mnohé z toho, co používá klasické, neobjektové programování, ale v němž se základní úvahy o koncepci a konstrukci programu ubírají naprosto jinými cestami, než na které byli doposud zvyklí. Metody Zde se objevuje drobný terminologický guláš. Java převzala terminologii jazyka C++ a nehovoří o posílání zpráv, s nímž přišli zakladatelé objektově orientovaného programování, ale o volání metod, které je bližší programátorům pracujícím v klasických jazycích. Pokud jste již někdy programovali, tak pro ty z vás, kteří programovali v Baltíkovi, budou metody něco podobného jako Baltíkovi pomocníci, ti, kteří programovali v jiných jazycích, je mohou považovat ze ekvivalenty procedur a funkcí. Metoda je část programu, kterou instance spustí v reakci na obdržení zprávy. Každá zpráva má v programu přiřazenu svoji vlastní metodu. Programátor v metodě definuje, jak bude objekt na příslušnou zprávu reagovat. Svým způsobem je jedno, jestli řeknete, že instanci pošlete zprávu nebo že zavoláte její metodu. Termín zpráva budu proto používat spíš v souvislosti s popisem chování programu (tj. prakticky celou tuto kapitolu), termínu metoda pak budu dávat přednost ve chvíli, kdy budu hovořit o konkrétní části programu realizující odpověď na zaslání příslušné zprávy. Jak jsem ale řekl, oba termíny jsou si svým způsobem ekvivalentní. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 42 z 433 Kapitola 2: Pracujeme s třídami a objekty 2.2 43 Analogie Prozatím jsme se o třídách, objektech, zprávách a atributech bavili jako o něčem abstraktním, co se sice projevuje způsobem, který dokážeme vnímat, ale ne vždy jej také pochopit. Zkusím vám proto vše připodobnit k něčemu hmatatelnému – třeba tato představa některým z vás pomůže při chápání některých konstrukcí, které budeme ve zbytku knihy vytvářet. Nepatříte-li k těm, kteří mají rádi teoretické koncepty ilustrované analogiemi z reálnějšího světa a domníváte-li se naopak, že by taková analogie vše jen zatemnila, klidně tuto podkapitolu přeskočte. Představte si svůj projekt jako město robotů. Každá třída v něm vystupuje jako továrna vyrábějící roboty. Jejími instancemi jsou vozidla vybavená robotími osádkami – takovou robotí pracovní četou. Jedna továrna může vyrábět třeba vozidla s četou hasičů, druhá vozidla s četou opravářů, třetí vozidla s četou malířů. Každá továrna vyrábí všechna vozidla stejná s naprosto stejnými osádkami. Každý člen osádky vozidla je specializovaný na jedinou funkci, kterou je reakce na konkrétní zprávu a představuje tak ekvivalent metody. Vozidlo představující instanci je vybaveno radiostanicí, takže může z okolního světa přijímat zprávy. Když přijme zprávu, která vyžaduje splnění některého z úkolů, pro které bylo postaveno, pověří příslušného specializovaného robota (metodu), aby úkol splnil. Robot může při plnění úkolu posílat zprávy libovolné osádce (včetně té svojí), a požádat ji, aby vykonala nějakou akci potřebnou pro splnění úkolu. Ikonou, kterou vidíte vlevo, budu v dalším textu uvozovat poznámky, které se budou odvolávat na výše uvedenou analogii. Budete-li si je chtít někdy projít všechny jednu po druhé, podívejte se do rejstříku na heslo analogie – tam najdete seznam stránek, na nichž se vyskytují. 2.3 Třídy a jejich instance Vytváříme svou první instanci V jazyku Java vytváříme instance tříd zasláním zprávy sestavené z klíčového slova new, za nímž uvedeme název třídy, jejíž instanci vytváříme, a dvojici kulatých závorek. Tím zavoláme speciální funkci, které říkáme konstruktor. Konstruktor @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 43 z 433 44 Myslíme objektově v jazyku Java 1.5 požadovanou instanci vytvoří a vrátí odkaz, prostřednictvím nějž se budeme na nově vytvořenou instanci obracet. Vyzkoušíme si vše v praxi. Budeme pokračovat v práci na projektu 02_Objekty, který jsme v minulé kapitole otevřeli. V tomto projektu je 6 tříd: Plátno – Instance třídy Plátno je jedináček. Představuje plátno, na kterém se budou zobrazovat námi vytvořené tvary. Obdélník, Trojúhelník a Elipsa – Jejich instance představují objekty, které lze na plátno nakreslit a z nichž můžeme sestavovat složitější tvary. Barva – Třída má právě devět předem vytvořených instancí představujících základní barvy: černou, modrou, červenou, fialovou, zelenou, azurovou, žlutou, bílou a krémovou (další již vytvořit nedovolí). Plátno i zobrazované tvary musí být nakresleny jednou z těchto barev. Každá třída má definovanou svoji oblíbenou (implicitní) barvu. Časem si ukážeme, jak barvu instance ovlivnit. Směr – Instance třídy Směr představují čtyři hlavní a čtyři vedlejší světové strany, tj. východ, severovýchod, sever, severozápad, západ, jihozápad, jih a jihovýchod. Pomocí těchto instancí je možno definovat směr, do kterého bude vytvářený trojúhelník natočen. Nezadáme-li směr natočení, vytvoří se rovnoramenný trojúhelník s vrcholem otočeným na sever. Vyjmenované třídy jsou v diagramu tříd zobrazeny jako vodorovně rozdělené obdélníky, v jejichž horní části je zapsán název třídy. Jazyk UML, od jehož pravidel se způsob kreslení tříd v BlueJ odvozuje, sice definuje i obsah spodní části, ale autoři prostředí BlueJ vás nechtěli hned zpočátku zahrnovat záplavou informací a v zájmu maximální přehlednosti diagramu tříd ponechali spodní část obdélníku prázdnou. Zkuste nyní vytvořit např. instanci obdélníku. Klepněte pravým tlačítkem na obdélník představující třídu Obdélník. Rozbalí se místní nabídka, v jejíž horní části je seznam konstruktorů uvozených operátorem new. Klepněte na ten z uvedených konstruktorů, který má za sebou prázdné závorky (viz obr. 2.1). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 44 z 433 Kapitola 2: Pracujeme s třídami a objekty 45 Obrázek 2.1 Vytvoření instance třídy Obdélník BlueJ otevře dialogové okno (viz obr. 2.2), v němž vás seznámí se základními informacemi o vyvolávaném konstruktoru a jím vytvářené instanci a zároveň se vás zeptá, jak budete chtít pojmenovat odkaz na vytvářený objekt, přičemž vám nabídne jméno odvozené od jména třídy, jejíž instanci vytváříte. Obrázek 2.2 Dialogové okno po zavolání bezparametrického konstruktoru. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 45 z 433 46 Myslíme objektově v jazyku Java 1.5 Pravidla pro tvorbu identifikátorů v jazyce Java Jména odkazů, tříd a dalších pojmenovaných částí programu se označují termínem identifikátory, protože nám tyto části identifikují. Každý programovací jazyk definuje sadu pravidel, jimž musí jeho identifikátory vyhovovat. V jazyku Java musí identifikátory vyhovovat následujícím pravidlům: Smějí obsahovat pouze písmena, číslice a znaky „_“ (podtržítko) a „$“ (dolar). Za písmeno je přitom považován jakýkoliv znak, který sada UNICODE považuje za písmeno. Sem patří nejen písmena s diakritikou, ale také např. čínské či japonské znaky. Nesmějí začínat číslicí. Nesmějí být shodné s žádným klíčovým slovem, tj. s žádným ze slov: abstract const extends if native short throw boolean continue final implements new static throws break default finally import package strictfp transient byte do float instanceof private super try case double for int protected switch void catch else goto interface public synchronized volatile class enum char long return this while Délka identifikátorů (počet jejich znaků) není v jazyku Java omezena – to u řady jiných programovacích jazyků neplatí. Java patří mezi jazyky, kterým záleží na tom, zda napíšete identifikátor velkými či malými písmeny. Identifikátory něco, Něco a NĚCO jsou proto chápány jako tři různé identifikátory. Jak jsem již řekl, podle definice jazyka patří mezi písmena všechny znaky včetně českých, ruských či japonských, nicméně některá prostředí se s neanglickými znaky nekamarádí. Ověřte si proto tuto skutečnost před tím, než začnete identifikátory s diakritikou používat. Java sice délku identifikátorů nijak neomezuje, avšak prostor pro zobrazení jména odkazu je omezený. Při zadávání názvů ručně vytvářených instancí proto dávejte přednost kratším názvům (tak do 10 znaků), které se zobrazí celé. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 46 z 433 Kapitola 2: Pracujeme s třídami a objekty 47 Vytváříme svou první instanci – pokračování Máme tedy otevřené dialogové okno z obr. 2.2 na straně 45. Protože nemáme žádný pádný důvod definovat nějaký zvláštní vlastní název, můžeme akceptovat název, který nám program nabídne. Po potvrzení jména odkazu vytvoří BlueJ instanci třídy Obdélník a odkaz na ni umístí do zásobníku odkazů, který se nachází pod diagramem tříd. Program nikdy nemůže pracovat s instancí, ale vždy pouze s odkazem na ni. Použijeme-li naši analogii, pak bychom mohli říci, že nikdy nemůžete mít v držení celou pracovní četu (= instanci), protože byste ji ani neunesli. Budete mít vždy pouze její telefonní číslo (= odkaz), na které jí můžete zavolat, kdykoliv od ní budete něco potřebovat (budete jí chtít poslat zprávu). Obrázek 2.3 Odkaz na první instanci v zásobníku odkazů Položky v zásobníku odkazů jsou zobrazeny jako obdélníky se zaoblenými rohy obsahující dvouřádkový popisek: v prvním řádku je uvedeno jméno odkazu a v druhém řádku jméno třídy, na jejíž instanci daná položka odkazuje. Na počátku kapitoly jsme si řekli, že se při vytváření instance zavolá speciální funkce nazývaná konstruktor. Konstruktor instancí třídy Obdélník je napro@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 47 z 433 48 Myslíme objektově v jazyku Java 1.5 gramován tak, že při vytváření instance zároveň zařídí, aby se na plátně zobrazil příslušný obrázek a pokud plátno ještě neexistuje, tak aby se vytvořilo (tj. požádá třídu Plátno, aby vytvořila svoji instanci), protože jinak by nebylo na co kreslit. Vedlejším efektem vytvoření první instance tedy je, že se otevře okno plátna a na něm bude zobrazen náš obdélník (viz obr. 2.4). Obrázek 2.4 Okno plátna s nakresleným obdélníkem Pokud okno plátna nevidíte, bude asi schované pod nějakým jiným oknem – dost možná právě pod oknem BlueJ. Zobrazíte je tak, že na panelu úloh klepnete na jeho tlačítko (naše plátno se chová jako samostatná aplikace, tak má na panelu úloh svoje tlačítko), nebo tak, že budete postupně minimalizovat či posouvat otevřená okna, až okno plátna najdete. Třída Plátno je zrovna třídou, která vám nedovolí vytvořit její instanci prostřednictvím konstruktoru (tj. pomocí operátoru new), protože trvá na tom, aby její instance byla jedináček (chce, aby se všechny obrazce kreslili na stejné plátno). To ale naštěstí nevadí, protože se s ní třídy Obdélník, Elipsa a Trojúhelník (přesněji jejich konstruktory) dokáží dohodnout, aby vše fungovalo tak, jak to fungovat má. Zanedlouho si ukážeme náhradní způsob, jak získat odkaz na instanci plátna. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 48 z 433 Kapitola 2: Pracujeme s třídami a objekty 49 Posíláme instanci zprávu Nyní zkusíme nově vytvořenému obdélníku poslat nějakou zprávu. Klepněte proto pravým tlačítkem myši na odkaz na náš nově vytvořený obdélník (tj. na zaoblený obdélník v zásobníku odkazů). Rozbalí se poměrně dlouhá místní nabídka, která má v horní části černé položky zastupující zprávy (vysvětlení proč vypadají právě takto si necháme na později) a v dolní části červené položky zastupující příkazy pro vývojové prostředí. Najeďte myší do horní části a zadejte příkaz void posunVpravo() (viz obr. 2.5) – a ejhle, obdélník se po plátně opravdu posune vpravo. Obrázek 2.5 Poslání zprávy odkazované instanci Pošlete instanci další zprávy. Prozatím se však omezte na zprávy void posunVpravo(), void posunVlevo(), void posunVzhůru() a void posunDolů(). (O tom, proč tyto zprávy začínají právě slovem void, si povíme za chvíli.) @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 49 z 433 50 Myslíme objektově v jazyku Java 1.5 Vytváříme další instance Říkali jsme si, že běžná třída může mít více instancí. Tak si to hned vyzkoušíme a vytvoříme další obdélník – požádejte třídu Obdélník stejným způsobem o vytvoření další instance. Potvrďte opět nabízený název a zkontrolujte, že se v zásobníku odkazů objevil příslušný odkaz a že se v levém horním rohu plátna opravdu nakreslil druhý obdélník. Nyní si již můžete vybírat mezi dvěma adresáty svých zpráv. Z čí místní nabídky zadáte příkaz k poslání zprávy, té instanci se zpráva odešle a ta instance na ni zareaguje. Vytvořte instance dalších tříd. Pošlete jednotlivým instancím zprávy void posunVpravo(), void posunVlevo(), void posunVzhůru() a void posunDolů() a ověřte, že na každou zprávu reaguje ten objekt, kterému jste ji poslali. Posunu obrazců se dosahuje obdobným způsobem, jakým byste postupovali vy, kdybyste je kreslili pastelkami na papír. Máte-li některý z obrazců posunout do nové pozice, musíte jej nejprve vygumovat ve staré pozici a pak nakreslit v pozici nové. Budou-li se však dva obrazce překrývat, odmaže se při mazání přesouvaného obrazce i část obrazce, se kterým se překrývá. Protože o sobě obrazce nevědí, nemohou tuto nepříjemnost samy napravit. Odmazanou část obrazce obnovíte tak, že obrazec požádáte, aby se překreslil, tj. pošlete mu zprávu void nakresli(). Tuto nepříjemnou vlastnost odstraníme až ve třetí kapitole, kdy se seznámíme s nástroji, s jejichž pomocí bude možné problém vyřešit. Když budete mít vytvořených instancí více, už se vám do oblasti vyhrazené pro zásobník instancí nevejdou. Zásobník se proto doplní po stranách o dvě posunové šipky (viz obr. 2.6). Klepnutím na ně obsah zásobníku posunete. Tak budete moci zásobníkem procházet, i když v něm bude větší počet instancí. Rušení instancí a správa paměti Pokud již nějaký odkaz v zásobníku odkazů nepotřebujete, můžete jej odstranit. K tomu je třeba zadat v jeho místní nabídce povel Odstranit (naleznete jej ve spodní části nabídky – viz obr. 2.6). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 50 z 433 Kapitola 2: Pracujeme s třídami a objekty 51 Obrázek 2.6 Zrušení odkazu na objekt Po zadání povelu odkaz ze zásobníku zmizí. To však ještě neznamená, že byl objekt doopravdy zrušen. Jak jsem již řekl, v zásobníku nejsou skutečné objekty, ale pouze odkazy na ně. O tom, jestli a kdy má být příslušný objekt doopravdy zrušen, rozhoduje správce paměti, což je speciální program, který má na starosti přidělování paměti nově vznikajícím objektům a rušení objektů, které již nikdo nepotřebuje. Správce paměti je v anglických textech někdy označován jako garbage collector, což v překladu znamená sběrač odpadků neboli popelář. Programátoři si na něm totiž nejvíce cení, že po nich dokáže uklidit. Někteří autoři tento termín nepřekládají, jiní používají termín sběrač neplatných objektů. Jak jsem již ale řekl, tento program nemá na starosti pouze úklid paměti, ale také její přidělování, a proto dávám přednost termínu správce paměti. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 51 z 433 52 Myslíme objektově v jazyku Java 1.5 Odstranění odkazu ze zásobníku odkazů nebude mít žádný vliv na podobu plátna. Obrazce na plátně nemůžeme s objekty ztotožnit, jsou totiž pouze výsledkem nějaké činnosti těchto objektů. Budete-li chtít objekt z plátna smazat, pošlete mu zprávu void smaž(). Tuto zprávu mu však musíte poslat před tím, než jej ze zásobníku odstraníte, protože pak již nebudete mít k dispozici odkaz, takže nebudete mít komu zprávu poslat. „Ukliďte“ zásobník odkazů – odstraňte z něj odkazy na všechny doposud vytvořené objekty, ale před tím je nejprve smažte. 2.4 Zprávy žádající o hodnotu Zprávy, které jsme doposud vytvořeným instancím posílali, požadovaly po instancích nějakou (viditelnou) akci. V programech však často nepotřebujeme, aby instance něco provedla, ale aby nám něco prozradila (většinou o sobě). Abychom mohli s touto informací pracovat, předáváme při poslání zprávy žádající informaci také požadavek, jakého typu má daná informace být (např. číslo, text, apod.). V místních nabídkách instancí a tříd začínají příkazy, jejichž zadáním vysíláme zprávy, názvem datového typu, který má mít vrácená informace. Příkazy vysílající zprávy, které po instanci žádnou informaci nepožadují a spokojí se s tím, že oslovená instance provede nějakou akci, začínají magickým slůvkem void (anglicky prázdno). Příkazy vysílající zprávy, které po instanci chtějí, aby nám vrátila nějakou informaci, začínají názvem typu, který bude mít vracená hodnota. Datové typy Než se rozhovoříme o tom, jak pracovat se zprávami, které vracejí hodnoty, musíme si nejprve povědět něco o datových typech. Typ údaje popisuje, co je daný údaj zač. Svůj typ mají veškerá data, se kterými program pracuje. Java (a naprostá většina ostatních moderních jazyků) trvá na tom, aby byl u každého údaje předem znám jeho typ. Za to se nám odmění tím, že bude pracovat mnohem rychleji (nemusí v průběhu výpočtu bádat nad tím, co je které „dato“ zač) a navíc odhalí řadu našich chyb již v zárodku. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 52 z 433 Kapitola 2: Pracujeme s třídami a objekty 53 Java rozděluje datové typy na primitivní a objektové. Primitivní datové typy, mezi něž patří např. čísla, jsou zabudovány hluboko v jazyku a jejich chování je pevně dané. K vytvoření hodnot primitivních datových typů nepotřebujeme konstruktor, ale na druhou stranu jim zase nemůžeme posílat žádné zprávy. Objektové datové typy jsou nám již dobře známé třídy. Těch bývá v programu definováno většinou mnohem víc. V našem úvodním pidiprojektu je jich definováno 6, v rozsáhlejších projektech jich mohou být definovány stovky a tisíce. Jenom ve standardní knihovně jich je definováno přes 4000 (primitivních datových typů je 8). Objektové datové typy (tj. třídy) si může každý programátor nadefinovat sám. S jistou rezervou bychom mohli říci, že objektové programování spočívá v návrhu a implementaci těch správných objektových datových typů. V nejbližších kapitolách se budeme setkávat s následujícími datovými typy: Primitivní datové typy int označuje typ celých čísel, jejichž hodnoty se mohou pohybovat přibližně v rozsahu ± 2 miliardy (přesně od -2 147 483 648 do +2 147 483 647). Název typu je zkratkou ze slova integer (= celé číslo). Je to nejpoužívanější datový typ. boolean definuje typ logických hodnot, které mohou nabývat pouze hodnot true (pravda, ano, …) a false (nepravda, ne, …). Název tohoto typu nám připomíná matematika George Boola, který se v 19. stoletý zabýval logikou a na jehož počest se práce s logickými výrazy označuje jako Booleova algebra. double označuje typ reálných čísel. Čísla typu double se v Javě pamatují s přesností na 15 platných číslic a v rozsahu do 10308 (číslo s 308 nulami). Své jméno dostal od toho, že v době svého zavedení (šedesátá léta minulého století) definoval čísla s dvojitou přesností oproti číslům tehdy většinou používaným. Nepleťte si platné číslice a desetinná místa. Např. číslo 0,00123 má pět desetinných míst, ale pouze tři platné číslice. Na druhou stranu číslo 12300 nemá žádné desetinné místo a může mít tři až pět platných číslic podle toho, jsou-li závěrečné nuly přesné, anebo vznikly zaokrouhlením. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 53 z 433 54 Myslíme objektově v jazyku Java 1.5 Objektové datové typy String definuje typ znakových řetězců, tj. posloupností znaků, které chápeme jako jediný objekt. Aby program zadávané řetězce správně rozpoznal, musíte tyto texty zadávat vždy uzavřené mezi uvozovkami – např. "Znakový Řetězec". String je druhý nejpoužívanější datový typ. Své jméno dostal podle anglického sova string = řada, série, řetěz(ec), provázek, … Mezi objektovými typy má typ String výjimečné postavení, protože je „zapuštěn“ hluboko do definice jazyka a tvůrci jazyka mu oproti ostatním objektovým typům přidali některé dodatečné vlastnosti, které u jiných datových typů nenajdeme. S řadou z nich se seznámíte již v této a příští kapitole. Barva, Elipsa, Obdélník, Plátno, Směr, Trojúhelník jsou objektovými typy, s nimiž jste se již seznámili v diagramu tříd projektu. Na rozdíl od typu String, který je součástí standardní knihovny, jsem pro vás tyto datové typy naprogramoval já a vy od příští kapitoly začnete programovat vlastní. Těžiště objektového programování spočívá v umění definovat správné objektové datové typy a v umění správně definovat jejich vzájemnou spolupráci. Před chvílí jsem vám říkal, že objektových datových typů je ve standardní knihovně přes 4000 a prozatím jsme si pověděli pouze o jednom. S dalšími objektovými datovými typy ze standardní knihovny se začneme seznamovat až v druhé části této učebnice, kdy již budou vaše znalosti dostatečné k tomu, abyste schopnosti těchto typů dokázali náležitě využít. Vracení hodnot primitivních typů Tak dost již teoretických řečí a pojďme spolu zase něco dělat. Budeme-li chtít po nějakém objektu, aby nám prozradil svoje souřadnice na plátně, pošleme mu postupně zprávy int getX() a int getY(), po nichž nám objekt vrátí vždy celé číslo (int) udávající jeho vzdálenost v bodech od levého horního rohu plátna. Budeme-li naopak chtít, aby nám prozradil svůj rozměr, pošleme mu postupně zprávy int getŠířka() a int getVýška(), po nichž nám objekt vrátí vždy celé číslo udávající příslušný rozměr v bodech. Informaci, kterou požadujeme, nám objekt vrátí v textovém poli dialogového okna, které se v reakci na vyslání zprávy otevře. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 54 z 433 Kapitola 2: Pracujeme s třídami a objekty 55 Tady se možná někteří z vás podivili, proč je součástí názvů uvedených zpráv slovíčko get. Je to všeobecná konvence, podle které názvy zpráv, na které příslušná instance odpovídá vrácením nějaké hodnoty popisující její stav, začínají předponou get za níž následuje název oné vracené informace. Je-li vracená hodnota logickou hodnotou, používá se místo předpony get předpona is. Na konci minulé kapitoly jsme zásobník i plátno vyčistili, takže můžeme začít znovu – vytvořte nový obdélník a nazvěte jej o1. Tomuto obdélníku pak zašlete zprávu int getŠířka(). Jako odpověď otevře BlueJ dialogové okno, v němž nám požadovaný údaj prozradí (viz obr. 2.7). Obrázek 2.7 Vrácená hodnota po zaslání zprávy int getŠířka() objektu o1 Hodnota, kterou instance vrací, bývá označována tzv. návratová hodnota. Někdy se používá také termín výstupní hodnota. Vracení hodnot objektových typů Výše popsané zprávy vracely celočíselnou hodnotu, tj. hodnotu primitivního typu. Nic však nebrání tomu, aby zprávy požádaly o hodnoty objektových typů, přesněji odkazy na tyto hodnoty. (Jak jsme si již několikrát řekli, program nikdy nepracuje s instancemi, ale vždy pouze s odkazy na ně.) Práce s takovýmito návratovými hodnotami je však trochu komplikovanější, protože k převzetí odkazu musíme ještě zadat název položky, která bude vytvořena v zásobníku odkazů a do které bude odkaz na požadovaný objekt uložen. Zprávou požadující vrácení hodnoty objektového typu je např. zpráva getBarva(), kterou mají všechny tři geometrické tvary na počátku svého seznamu. Podívejme se nyní, jak budeme postupovat při poslání této zprávy a zejména pak zpracování obdrženého výsledku. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 55 z 433 56 Myslíme objektově v jazyku Java 1.5 V zásobníku odkazů máme odkaz o1 ukazující na obdélník, jehož barvu chceme zjistit. Pošleme proto tomuto obdélníku zprávu getBarva(). Obdélník zprávu zpracuje a vrátí odkaz na svoji barvu. BlueJ pak otevře dialogové okno, v němž nás šipkou v textovém poli upozorní na to, že vrácenou hodnotou byl odkaz na objekt. Oproti minulé verzi dialogového okna ožijí v tomto okně dvě tlačítka po pravé straně: Tlačítko Prohlížet nám umožní se podívat do útrob vraceného objektu. Prozatím si jej nebudeme všímat – budeme si o něm povídat v kapitole Výlet do nitra instancí na straně 68. Tlačítko Získat odkaz je určeno pro převzetí vraceného odkazu a jeho uložení do zásobníku odkazů. S tím si budeme „hrát“ nyní. Obrázek 2.8 Předání návratové hodnoty objektového typu Chtěli jsme odkaz, tak stiskneme tlačítko Získat odkaz. BlueJ otevře dialogové okno, v němž se nás zeptá na požadované jméno (identifikátor) nově vzniklého odkazu. Protože víme, jaká je barva obdélníku, můžeme odkaz nazvat přímo názvem této barvy a stiskem OK svoji volbu potvrdit. Obrázek 2.9 Zadání názvu vráceného odkazu BlueJ pak vzápětí uloží odkaz do zásobníku, kde s ním můžeme pracovat podobně jako s odkazy na objekty, o jejichž vytvoření jsme požádali konstruktor. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 56 z 433 Kapitola 2: Pracujeme s třídami a objekty 57 Použijeme-li naši analogii (viz podkapitola Analogie na straně 43), pak bychom mohli říci, že pošleme-li někomu zprávu, v níž žádáme hodnotu objektového typu, dostaneme vždy pouze telefonní číslo na příslušnou instanci. Obrázek 2.10 S odkazem získaným zasláním zprávy můžeme pracovat jako s jakýmkoliv jiným Vzácnou výjimkou mezi objektovými typy je typ String. Když jsem se o něm poprvé zmiňoval, říkal jsem, že se v některých situacích chová obdobně jako primitivní datové typy. Jednou z takovýchto situací je v programu BlueJ předávání návratové hodnoty, při němž se String chová současně jako primitivní typ, protože zobrazí požadovanou hodnotu, a současně jako objektový datový typ, protože nám umožní získat odkaz. Zkuste např. poslat odkazu na červenou barvu zprávu String getNázev(). V dialogovém okně, které pak BlueJ otevře, uvidíte jak hodnotu tohoto řetězce, tak živá tlačítka nabízející možnost prohlížení a získání odkazu. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 57 z 433 58 Myslíme objektově v jazyku Java 1.5 Obrázek 2.11 Při vracení objektu typu String se zobrazí i příslušný řetězec 2.5 Parametry a jejich typy Prozatím jsme se omezovali na používání konstruktorů, jejichž závorky za názvem konstruktoru byly prázdné. Tyto tzv. bezparametrické konstruktory vytvářely obrazce ve stále stejném místě plátna, stále stejně veliké a pokaždé stejně vybarvené. Nyní si ukážeme, jak tento nudný stereotyp změnit. Budeme modifikovat chování konstruktorů prostřednictvím parametrů, s jejichž pomocí budeme moci všechny výše uvedené charakteristiky nastavit. Prostřednictvím parametrů předáme konstruktoru hodnoty, kterými budeme specifikovat některé naše požadavky na vytvářené instance. Někteří autoři používají pro parametry termín argument, občas se také můžete setkat s termínem vstupní hodnota jako opozitum k výstupní hodnotě vracené po zaslání některých zpráv. Jak jste si mohli všimnout v místních nabídkách, jednotlivé konstruktory se liší tím, kolik parametrů mají v závorkách uvedeno a které to jsou. Při definici konstruktoru proto tvůrce uvede v závorkách za názvem třídy čárkami oddělený seznam parametrů, které je konstruktor schopen zpracovat. Definice několika verzí konstruktorů s různými sadami typů parametrů bývá označována jako přetěžování daného konstruktoru. Jednotlivé verze se pak nazývají přetížené. Aby program dokázal s předanými hodnotami co nejefektivněji pracovat, musí vědět, co jsou zač. Je např. zřejmé, že např. konstruktor trojúhelníků bude zcela jinak pracovat s barvou svého objektu, jinak se směrem, do nějž má být vytvářený @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 58 z 433 Kapitola 2: Pracujeme s třídami a objekty 59 trojúhelník natočen a zcela jinak s číslem představujícím jeho rozměr nebo souřadnici. Seznam parametrů v závorkách proto obsahuje nejenom názvy (identifikátory) jednotlivých parametrů, které nám napoví jejich význam, ale také jejich datové typy, podle nichž počítač pozná, jak má s daným parametrem pracovat. Vyvolání konstruktoru s parametry Ukažme si vše na příkladu. Zkusíme vytvořit další instanci třídy Obdélník, u které si objednáme její umístění i velikost. Pošleme proto zprávu, jež spustí konstruktor, který nám umožní zadat jak x-ovou a y-ovou souřadnici vytvářeného obdélníku, tak jeho rozměry. Vhodná zpráva je v nabídce uvedena jako příkaz (viz obr. 2.12) new Obdélník( x, y, šířka, výška ) Obrázek 2.12 Zavolání konstruktoru umožňujícího zadat požadovanou souřadnici a rozměr Po zadání příslušného povelu se otevře velké dialogové okno (viz obr. 2.13), v němž nás BlueJ vyzve nejenom k zadání (nebo alespoň potvrzení) názvu odkazu na vytvářený objekt, ale také k zadání hodnot jednotlivých parametrů. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 59 z 433 60 Myslíme objektově v jazyku Java 1.5 Obrázek 2.13 Okno konstruktoru s parametry Horní část okna zobrazuje popis funkce volaného konstruktoru spolu s popisem významu jednotlivých parametrů. Neděste se toho, že popis je trochu kryptický (zašifrovaný) – je to opsaná část programu sloužící k automatizované tvorbě dokumentace. Aby Java mohla tuto dokumentaci vytvořit, požaduje dodržení jistých konvencí, které sice trochu ztěžují přímé čtení programu, ale na druhou stranu umožňují právě vytvoření profesionální dokumentace. Tvorbě dokumentace bude věnována pasáž Automaticky generovaná dokumentace na straně 160. Prozatím si pamatujte, že za každým @param následuje název parametru a jeho popis. V této části nemusí být vždy uvedeny všechny parametry a dokonce v ní může chybět i popis. Bude zde vždy jen to, co se tvůrce programu rozhodl popsat. Berte proto tento návod pouze jako pomůcku a smiřte se s tím, že programy tvůrců, kteří jsou líní dělat dokumentaci, takovouto nápovědu nabízet nebudou. Snažte se k nim nezařazovat a své programy vždy řádně dokumentujte. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 60 z 433 Kapitola 2: Pracujeme s třídami a objekty 61 Zde bych vás chtěl upozornit na poslední řádek horní části, který je těsně nad dělící čarou a je vysazen tučně. Zde je zobrazena hlavička konstruktoru, která specifikuje to nejdůležitější, co potřebujete o konstruktoru vědět. V hlavičce je uveden název konstruktoru (a tím i název třídy, jejíž instanci vytváříme) následovaný závorkami se seznamem všech parametrů (na rozdíl od komentáře, kde může něco chybět, tady nic chybět nesmí). Je-li parametrů více, jsou jejich deklarace odděleny čárkami. Všimněte si, že u každého parametru je uveden nejprve typ a za typem název (identifikátor) daného parametru. Takto budete od příští kapitoly uvádět parametry i vy ve svých programech. Autoři BlueJ se snažili ulehčit orientaci v situacích, kdy je zadávaných parametrů více a zopakovali typ i název parametru vpravo vedle vstupního pole, do nějž se má zadat jeho hodnota. Jednotlivé prvky okna jsme si probrali a můžeme začít vyplňovat. Pro nově konstruovaný obdélník zadáme název o2. Dejme tomu, že vytvoříme obdélník tak, aby zabral celé plátno, které má v počátečním stavu rozměr 300×300 bodů. Zadáme proto parametry podle obrázku 2.14. Obrázek 2.14 Vyplněné okno parametrického konstruktoru Po zadání příkazu se na plátně objeví vytvořený obdélník. Protože zabírá celé plátno a protože je červený, přebarví celé plátno na červeno. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 61 z 433 62 Myslíme objektově v jazyku Java 1.5 1. Vytvořte další obrazce, kterým zadáte požadované souřadnice a rozměry. 2. Uveďte plátno do (zdánlivě) počátečního stavu tím, že obdélníku zabírajícímu celé plátno pošlete zprávu void smaž(). Po této zprávě obdélník překreslí celou svoji plochu barvou pozadí plátna, čímž automaticky všechny ostatní zobrazené tvary smaže. Parametry objektových typů Z konstruktorů, které každá ze tříd Obdélník, Elipsa a Trojúhelník nabízí, jsme doposud použili pouze dva: bezparametrický konstruktor a konstruktor, který umožnil zadat počáteční pozici a rozměr obdélníku. Při volání zbylých konstruktorů je totiž třeba zadat parametr objektového typu – a to se nyní naučíme. Prozatím jsem vám všechno předváděl na obdélnících. Aby to ostatním obrazcům nebylo líto, budeme si chvíli hrát s nimi. Každý z tvarů má definovanou svoji implicitní barvu, kterou se na plátno nakreslí. Budeme-li chtít vytvořit obrazec jiné barvy, musíme zavolat konstruktor, který je schopen převzít požadovanou barvu jako parametr, a vytvářený obrazec s ní vybarvit. Hodnotu objektového typu můžeme zadat dvěma způsoby: Máme-li v zásobníku odkaz na příslušnou hodnotu, stačí klepnout myší do vstupního textového pole příslušného parametru a potom klepnout v zásobníku na tento odkaz. BlueJ sám zapíše potřebný text. Nemáme-li v zásobníku odkaz na předávanou hodnotu, musíme ji zapsat textově. Tento způsob zadávání se ale budeme učit až za chvíli. V zásobníku odkazů máme odkaz na červenou barvu. Rozhodneme-li se proto vytvořit ve středu plátna červený kruh o průměru 100 bodů, můžeme pro zadání barvy tento odkaz využít – viz obr. 2.15. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 62 z 433 Kapitola 2: Pracujeme s třídami a objekty 63 Obrázek 2.15 Objektový parametr můžeme zadat klepnutím na odkaz v zásobníku Posílání zpráv s parametry Ukážeme si nyní, že parametry můžeme využívat i při předávání zpráv. Konstruktory jsou pouze zvláštním druhem zpráv, takže většinu toho, co můžeme použít u konstruktorů, můžeme použít i u obyčejných zpráv. Přesouvání jsme si již užili dost, tak zkusíme něco jiného – např. změnit velikost objektu. Zkuste změnit rozměry „celoplátnového“ obdélníka o1 tak, aby zůstal tak široký jako plátno (tj. 300 bodů), ale aby byl pouze 20 bodů vysoký. Zadejte proto v jeho místní nabídce povel void setRozměr(šířka, výška) a v následně otevřeném dialogovém okně zadejte příslušné hodnoty jeho parametrů. Jak jste si jistě domysleli, stejně jako konvence s předponou get existuje i konvence s předponou set, kterou začínají názvy zpráv, které nastavují hodnoty ovlivňující stav instance. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 63 z 433 64 Myslíme objektově v jazyku Java 1.5 Protože upravovaný obdélník zabíral celé plátno, zmizely se změnou jeho rozměrů i všechny další tvary, které jste na plátno při řešení posledního úkolu nechali nakreslit. V poznámce na str. 50 jsem říkal, že obrazec znovu zobrazíte tím, že mu pošlete zprávu void nakresli(). To však samozřejmě není jediné řešení. Obrazec se překreslí také po zaslání kterékoliv ze zpráv, které mění jeho pozici nebo polohu. Pošlete i ostatním obrazcům v zásobníku zprávu, v níž je žádáte o změnu jejich pozice nebo rozměru. Pokud vás ještě neomrzelo posouvání objektů, můžete jim zkusit poslat i zprávy void posunVpravo(vzdálenost) nebo void posunDolů(vzdálenost), které posunou příslušný objekt o zadaný počet bodů požadovaným směrem. Stejně jako konstruktorům můžete parametry objektových typů předávat i zprávám. Každému z tvarů můžete např. poslat zprávu void setBarva(nová), v níž mu oznámíte, na jakou barvu se má přebarvit. Vytvořte pomocí bezparametrického konstruktoru instance elipsy a trojúhelníku a získejte instance jejich barev. Nastavujte pak různým obrazcům různé barvy. Zkuste pomocí příkazů vytvořit nějaký zajímavý obrázek. 2.6 Metody třídy Někdy potřebujeme zaslat zprávu, která se netýká jedné konkrétní instance, ale týká se celé třídy. Rodičovská třída totiž své instance po jejich zřízení neopouští, ale uchovává informace, které jsou pro všechny její instance společné, aby je instance mohly v případě potřeby využít. Zároveň také často poskytuje metody, které umožňují tyto informace ovlivnit nebo vykonat jinou akci, jež není svázána s žádnou její konkrétní instancí. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 64 z 433 Kapitola 2: Pracujeme s třídami a objekty 65 V naší analogii bychom mohli metody třídy definovat jako roboty, kteří pracují přímo v továrně. Stejně jako pracovní četě (=instanci) je i v továrně (=třídě) vyhrazen pro každou zprávu, kterou lze do továrny poslat, speciální robot (= metoda třídy), který zabezpečí správnou reakci na tuto zprávu. Podívejme se např. na náš předchozí příklad s geometrickými tvary. Na příkaz void přesunXXX() se obrazce přesunou o předem stanovaný počet obrazových bodů směrem XXX. Velikost tohoto kroku je přitom pro všechny instance dané třídy stejná – je to právě ten druh informace, kterou instance sdílejí prostřednictvím své rodičovské třídy. Rozhodneme-li se proto velikost tohoto implicitního přesunu změnit, musíme poslat zprávu celé třídě. Otevřete si znovu místní nabídku třídy Obdélník (viz např. obrázek 2.1 na straně 45). V její střední části si můžete všimnout dvou položek, jejichž zadáním posíláme zprávu celé třídě. Zasláním zprávy int getKrok() se třídy zeptáte na současnou velikost kroku, zasláním zprávy void setKrok(velikost) nastavíte novou velikost tohoto kroku. Tato velikost pak bude platit pro všechny instance dané třídy, tj. i pro ty, které vznikly před tím, než jste nastavili novou velikost kroku a které proto doposud používaly velikost původní. Otevřete místní nabídku některé třídy a zadejte v ní příkaz void setKrok(velikost). V následně otevřeném dialogovém okně zadejte novou velikost kroku – dejme tomu 100 bodů – a vyzkoušejte, jak budou jednotlivé instance reagovat. Všimněte si, že se krok změnil pro všechny (staré i nové) instance dané třídy, avšak instance jiných tříd na něj vůbec nereagují a dál se přesouvají původně nastavenou velikostí kroku. Zkuste znovu změnit velikost kroku, avšak tentokrát pro jinou třídu. Nastavte jí krok např. na 5 bodů a znovu vše vyzkoušejte. Velmi užitečnou metodou třídy bude pro vás při vašich experimentech metoda smaž() třídy Plátno. Po našich předchozích pokusech máte již plátno jistě zaplněné několika grafickými tvary. Zavolejte proto tuto metodu z místní nabídky třídy plátno a budete mít plátno čisté. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 65 z 433 66 Myslíme objektově v jazyku Java 1.5 Metoda smaž()pouze smaže plátno, tj. „natře“ je na aktuální barvu pozadí. V žádném případě neodstraňuje objekty z paměti – pouze smaže obrázek, který na plátno nakreslily. Můžete se o tom přesvědčit tak, že kterýkoliv z objektů, na nějž máte odkaz, požádáte, aby se znovu nakreslil, tj. zadáte v jeho místní nabídce příkaz void nakresli(). Metoda smaž() třídy Plátno není pravou metodou třídy. Měla by to být správně metoda konkrétního plátna. Protože však víme, že toto plátno je jedináček, je z jistého úhlu pohledu jedno, jak metodu deklaruji. V našem programu je definována jako metoda třídy proto, abyste k ní měli snadný přístup a nemuseli kvůli smazání plátna pracně získávat odkaz na jeho instanci. Přiznávám, že z hlediska správného programování je to nečisté, ale pro naše praktické účely je to výhodné. Třídy, které chtějí, aby jejich instance byly jedináčci, musí spolupracujícím třídám a jejich instancím nějak umožnit získat odkaz na tohoto jedináčka, aby s ním mohly komunikovat. Používají k tomu většinou metodu, která se tváří, že pro vás požadovaný odkaz vyrobí. Na rozdíl od konstruktorů však nezaručuje, že po každém zavolání vrátí odkaz na nově vytvořenou instanci. Naopak – klidně vám pokaždé vrátí to samé. Takovou metodu má i třída Plátno – vyvoláte ji zasláním zprávy Plátno getPlátno() a získáte od ní odkaz na třídního jedináčka. Získejte odkaz na instanci plátna a vyzkoušejte jeho metody. Nelekněte se však, když plátno po změně svého rozměru nebo barvy pozadí všechny objekty smaže. Jak jste si již mohli vyzkoušet, po zprávě void nakresli() se daný tvar znovu překreslí. 2.7 Instance versus odkaz V jazyku Java program nikdy neobdrží instanci, kterou vytvořil. Obdrží pouze odkaz na tuto instanci. Vlastní instance je zřízena někde ve zvláštní paměti, které se říká halda (anglicky heap). Ta je vyhrazena právě pro zřizování objektů a má ji na starosti správce paměti (garbage collector – doslova popelář). Ten se stará mimo jiné o to, aby instance, které již nikdo nepotřebuje, nezabíraly v paměti zbytečně místo. Aby mohl tuto správu provádět dostatečně efektivně, nikoho k haldě @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 66 z 433 Kapitola 2: Pracujeme s třídami a objekty 67 nepustí a program se proto musí k objektům na haldě obracet prostřednictvím příslušných odkazů. V klasických programovacích jazycích se o přidělování a uvolňování paměti staral programátor. Ale právě správa paměti byla zdrojem velice záludných chyb. Proto moderní jazyky svěřují správu paměti speciálnímu programu, který svoji práci zastane v převážné většině případů mnohem lépe než člověk. Produktivita programátorské práce tímto řešením stoupla několikanásobně. Jak jsem již řekl na počátku našich hrátek s geometrickými tvary, třída Plátno trvá na tom, že její instance bude jedináček. (Proto nám také nenabízí konstruktor.) Z toho tedy zákonitě vyplývá, že každé zaslání zprávy getPlátno() vrátí odkaz na stejnou instanci. Vyzkoušejte to. Pošlete třídě Plátno znovu zprávu getPlátno() a opět zadejte v dialogovém okně, že chcete získat odkaz. V zásobníku odkazů se tak objeví další odkaz, pro nějž nám BlueJ nabídne název plátno2. Máme tedy dva odkazy, o nichž tvrdíme, že odkazují na stejný objekt. Přesvědčme se o tom. Požádejte jeden odkaz, aby jím odkazovaná instance změnila svůj rozměr a pak o totéž požádejte druhý odkaz (zadáte samozřejmě jiný rozměr). Obě dvě žádosti ovlivní velikost jednoho a toho samého okna. Oba dva odkazy totiž ukazují na jednu a tu samou instanci. Nesměšujte nikdy instanci s odkazem na ni! Až budete s objekty pracovat ve svých programech, pamatujte, že budete mít vždy k dispozici pouze odkazy na objekty, s nimiž pracujete. Může se proto lehko stát, že si budete na několika místech pamatovat odkaz na stejný objekt, ale stejně dobře se může stát, že jediný dostupný odkaz na důležitý objekt omylem přemažete a objekt pak bude sice dál existovat (a třeba dělat nějakou neplechu), ale vy se k němu již nikdy nedostanete. Jak jsme si již říkali, v naší analogii bychom mohli odkaz chápat jako telefonní číslo na danou instanci. K vlastní instanci se nikdy nedostanete a přemažete-li či zahodíte toto telefonní číslo, ztratíte tak také jakoukoliv možnost instanci o něco požádat. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 67 z 433 68 2.8 Myslíme objektově v jazyku Java 1.5 Výlet do nitra instancí Zatím jsme si ukazovali, jak instance reagují na zprávy, které jim posíláme. Nic jsme si ale doposud neříkali o tom, jak instance pozná, kde se má nakreslit, odkud se má přesunout nebo jak zjistí, jakou má její obrazec barvu. Aby mohl objekt na naše zprávy správně reagovat, musí uchovávat informace popisující stav objektu. Tyto informace označujeme jako atributy. Své atributy mají jednotlivé instance, ale může je mít i celá třída. Rozlišujeme proto atributy instancí a atributy třídy. Atributy třídy všechny její instance sdílejí a jakákoliv jejich změna se ihned promítne do všech instancí dané třídy. Někdy se v literatuře setkáte také s termíny vnitřní proměnné nebo členské proměnné. Já však o nich budu v dalším textu hovořit téměř výhradně jako o atributech. Kdybychom se vrátili k naší analogii, mohli bychom si atributy představit jako zásuvky s informacemi. Atributy instancí má každá instance (četa) ve svém vozidle, atributy třídy jsou v informačním centru v továrně (=třídě). Změní-li některý z robotů (=metod) hodnotu uloženou v šuplíku v autě (tj. hodnotu atributu instance), ovlivní tato změna chování ostatních robotů (=metod), protože až se půjdou do šuplíku podívat, najdou tam hodnotu, kterou tam jejich kolega uložil. Změní-li kdokoliv hodnotu uloženou v šuplíku v továrně (tj. hodnotu atributu třídy), ovlivní tato změna chování všech, kdo se po této změně půjdou do tohoto šuplíku podívat, tj. i chování ostatních instancí a jejich metod. Atributy instancí Prozatím jsme o atributech hovořili pouze jako o něčem, co existuje, ale neměli jsme možnost se o tom přesvědčit. Hned to napravíme. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 68 z 433 Kapitola 2: Pracujeme s třídami a objekty 69 Protože nevím, jak moc jste experimentovali, nemohu zaručit, že se budou následující obrázky v knize shodovat s těmi, které vám bude ukazovat váš počítač. Chcete-li je synchronizovat, zavřete BlueJ a znovu jej otevřete (já to nyní udělám také). Tím počítač inicializujete a zařídíte, že by naše nejbližší obrázky měly zase vypadat stejně. Vytvořte bezparametrickým konstruktorem instanci obdélníku a nazvěte ji o1. Vyvolejte pravým tlačítkem myši její místní nabídku a zadejte v ní povel Prohlížet. Tím spustíte prohlížeč objektů (uživatelé Windows si mohou všimnout, že se na liště úloh objeví další aplikace), který otevře své okno a vypíše v něm hodnoty atributů příslušné instance (viz obr. 2.16). Obrázek 2.16 Okno prohlížeče atributů instance Prohlížeč v okně zobrazí jednotlivé atributy dané instance. U každého z nich uvede jeho typ a název. U atributů primitivních typů a typu String uvede navíc i jejich hodnotu. U atributů ostatních objektových typů uvede místo hodnoty pouze symbol označující, že se jedná o odkaz. Klepnete-li však na atribut objektového typu, ožije vpravo tlačítka Prohlížet, jehož stiskem můžete otevřít pro odkazovanou instanci obdobné okno, v jakém se zrovna nacházíte, a podívat se na hodnoty atributů tohoto atributu. Slovo private, které informaci o typu a názvu uvozuje, oznamuje, že atribut je soukromým majetkem instance, ke kterému nemá nikdo cizí přístup. O jeho významu si podrobněji povíme v dalších kapitolách. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 69 z 433 70 Myslíme objektově v jazyku Java 1.5 Opravit Obdélník o1 má 6 atributů: Atribut název uchovává odkaz na řetězec označující daný obdélník. Můžete si všimnout, že každý další obdélník má číslo ve svém názvu o jedničku větší. Tento atribut slouží především k tomu, abyste mohli při ladění programu snáze poznat, o který obdélník se jedná. Atributy xPos a yPos uchovávají pozici instance, přesněji pozici levého horního rohu opsaného obdélníku. Jejich hodnotu zjistíme posláním zpráv int getX() a int getY() a nastavujeme ji posláním zprávy void setPozice(x,y). Ve škole jste se nejspíš setkali pouze s opsanou kružnicí. Opsaný obdélník je něco obdobného – myslíme jím nejmenší obdélník, do kterého se celý obrazec vejde. Atributy šířka a výška uchovávají informace o rozměru dané instance – u našich tvarů jsou to opět informace o šířce a výšce opsaného obdélníku. Atribut barva uchovává odkaz na objekt, který nese všechny potřebné informace o barvě daného objektu. Mění-li se stav objektu (v našem případě mění-li se jeho poloha, velikost nebo barva), mění se i hodnoty jeho atributů. BlueJ zobrazované hodnoty atributů instancí průběžně aktualizuje. Nechte okno otevřené a zkuste požádat instanci, do jejíchž útrob nahlížíte, aby se někam posunula. Přesvědčte se, že se po splnění tohoto příkazu změní hodnoty atributů uchovávajících informaci o pozici instance. Zkuste vyvolat další metody a podívejte se, jak ovlivňují hodnoty jednotlivých atributů. Atributy třídy – statické atributy Atributy, které nám prohlížeč doposud zobrazoval, jsou atributy příslušné instance. Svoje atributy však může mít i celá třída. Atributy třídy pak všechny její instance sdílejí. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 70 z 433 Kapitola 2: Pracujeme s třídami a objekty 71 Atributy třídy bývají často označovány jako statické atributy, protože jsou v programu uvozeny slovem static. Zůstaneme-li u naší analogie, můžeme říci, že atributy třídy (šuplíky) jsou na pevném místě (v továrně), kam se na jejich obsah chodí jednotlivé instance dívat nebo jej měnit. Tyto atributy bychom proto mohli označit za statické, protože se nehýbají. Naproti tomu atributy instancí si každá instance „vozí s sebou“. Zobrazení okna s atributy třídy dosáhnete dvěma cestami: V diagramu tříd zadáte v místní nabídce příslušné třídy příkaz Prohlížet. V okně prohlížeče instancí stisknete tlačítko Ukázat atributy třídy. V obou případech odpoví BlueJ otevřením okna s atributy příslušné třídy. Obrázek 2.17 Okno prohlížeče atributů třídy Požádáte-li třídu o nahlédnutí do jejích atributů před tím, než po ní budete cokoliv chtít, najdete tam jenom nuly a prázdné odkazy (null). Je to proto, že třída ještě nebyla inicializovaná. Třída se inicializuje až ve chvíli, kdy jí pošlete nějakou zprávu nebo ji požádáte o vytvoření instance. Než vám třída vaše přání splní, rychle se inicializuje a od té chvíle začnou mít hodnoty jejích atributů smysl. Opravit Jak ukazuje obr. 2.17, třída Obdélník má tři atributy: Atribut IMPLICITNÍ_BARVA obsahuje odkaz na barvu, kterou bude mít vytvořený obdélník v případě, že jej vytvoříte pomocí konstruktoru, jemuž neza@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 71 z 433 72 Myslíme objektově v jazyku Java 1.5 dáváte požadovanou barvu jako parametr, tj. konstruktoru, který takovýto parametr nemá. Atribut krok obsahuje velikost implicitního kroku, o kterou se obrázek obdélníka posune po plátně v případě, zavoláme-li některou z bezparametrických posunových metod. Jeho velikost zjišťujeme a nastavujeme metodami int getKrok() a void setKrok(velikost), o nichž jsme hovořili v kapitole Metody třídy na straně 64. Atribut počet uchovává počet doposud vytvořených instancí. Odtud si konstruktor bere číslo, které přidává za název třídy na konec hodnoty ukládané do atributu název. Žáci se v tomto místě občas ptají, proč mezi statickými atributy je implicitní barva a proč tam nejsou implicitní poloha a implicitní rozměr. Důvody rozhodnutí autorů třídy pro zavedení či nezavedení toho či onoho atributu mohou být různé. Mne vedla k zavedení atributu IMPLICITNÍ_BARVA touha mít jeden atribut třídy objektového typu. Řadu začátečníků překvapí, když zjistí, že některá třída nebo instance má mezi svými atributy odkazy na instance téže třídy. Zamyslíte-li se ale nad tím, musíte připustit, že nic z toho, co jsme si doposud o atributech řekli, definici takovýchto atributů nezakazuje. Možná o tom nevíte, ale od samého počátku s takovýmito třídami pracujete. Podíváte-li se na atributy třídy Barva nebo Směr, zjistíte, že jejími atributy jsou právě odkazy na instance těchto tříd. Obrátíte-li se na naši analogii s vozidly osazenými roboty, pak atributy, které jsou odkazy na instance vlastní třídy, jsou vlastně zásuvky s telefonními čísly těchto instancí. Instanci nic nebrání v tom, aby si vozila telefonní čísla (=odkazy) na jiné instance té samé třídy a zrovna tak v tom nic nebrání ani třídě. Jak jsem již řekl, atributy třídy jsou pro všechny instance společné. Nezávisle na tom, odkud okno prohlížeče atributů třídy otevřete, otevře se vždy jedno a to samé okno. Budete-li mít proto vytvořené dvě instance a požádáte-li v obou z nich o zobrazení statických atributů, tak se při druhé žádost žádné nové okno neotevře, ale pouze se aktivuje dříve otevřené okno. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 72 z 433 Kapitola 2: Pracujeme s třídami a objekty 73 1. Vyzkoušejte reakci hodnoty atributu počet na vytvoření další instance. 2. Vyzkoušejte reakci hodnoty atributu krok na změnu zadanou voláním metody void setKrok(velikost). 3. Ověřte, že se při dalších žádostech o atributy třídy znovu aktivuje dříve otevřené (a doposud nezavřené) okno. 2.9 Přímé zadávání hodnot parametrů objektových typů Při zadávání hodnot parametrů objektových typů nemusíme být odkázáni pouze na to, jestli se nám podaří umístit do zásobníku odkazů odkaz na objekt, který chceme předat jako parametr. I objektové parametry můžeme zadávat přímo, i když je to trochu těžší, ale opravdu jen trochu. Abychom mohli odkaz na jakýkoliv objekt zadat přímo jako hodnotu parametru, musíme mít šanci se k němu dostat. Při tom můžeme využít jednou ze dvou skutečností: třída či instance má veřejný atribut, který na tento objekt odkazuje, existuje metoda, která vrací odkaz na požadovanou instanci. Veřejné atributy Jednodušší případ nastane ve chvíli, kdy víme, že odkaz na příslušný objekt lze získat z některého veřejného atributu třídy (atributu, u nějž nám prohlížeč prozradí, že je public). Pak stačí do příslušného vstupního pole zadat název příslušné třídy následovaný tečkou a názvem vyhlédnutého atributu (řekneme si třídě o odkaz na její veřejný atribut). Nesmíte přitom zapomenout na nutnost dodržování velikosti písmen. Podívejme se např. na atributy třídy Směr. Zadejte v její nabídce příkaz Prohlížet a případně si zvětšete okno tak, abyste veřejné atributy třídy viděli všechny. Prohlížeč nám ukáže, že tato třída má 16 veřejných atributů třídy, z nichž polovina se jmenuje plným názvem daného směru a druhá polovina jeho zkratkou (viz obr. 2.18). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 73 z 433 74 Myslíme objektově v jazyku Java 1.5 Obrázek 2.18 Přehled veřejných atributů třídy Směr Rozhodneme-li se proto vytvořit na souřadnicích [100;100] trojúhelník otočený na východ a pojmenovat jej tv, můžeme zadat parametry zprávy podle obrázku 2.19 – do pole pro zadání směru uvedeme Směr.V nebo Směr.VÝCHOD. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 74 z 433 Kapitola 2: Pracujeme s třídami a objekty 75 Obrázek 2.19 Přímé zadání směru Obdobně bychom postupovali i v případě, když bychom takto chtěli zadat některou z devíti povolených barev – i ty jsou totiž veřejnými atributy třídy Barva. Odkazy vrácené po zaslání zprávy Při zadávání odkazů na instance objektových datových typů můžeme využít i hodnot, které nám vrátí volání některých metod. Víme například, že všechny instance geometrických tvarů umějí reagovat na zprávu Barva getBarva() a vrátit svoji barvu. Musí to umět i právě vytvořený trojúhelník. Postup zadání takto získané hodnoty odkazu je velmi podobný tomu, který jsme použili před chvílí při zadávání odkazu na atribut: 1. Napíšeme název toho, na koho se obracíme. Voláme-li metodu třídy, napíšeme název třídy, voláme-li metodu instance, napíšeme název odkazu na tuto instanci. 2. Za název přidáme tečku. 3. Za tečku napíšeme název metody, kterou voláme, následovaný seznamem parametrů v kulatých závorkách. Nemá-li metoda parametry, budou závorky @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 75 z 433 76 Myslíme objektově v jazyku Java 1.5 prázdné. Má-li metoda parametry, musíme uvést ve správném pořadí seznam jejich hodnot oddělených čárkami. Vyzkoušejme si to. Vytvořme na souřadnicích [50;50] čtverec o straně 50 bodů, který bude mít stejnou barvu, jako před chvílí vytvořený trojúhelník tv. Zavoláme proto konstruktor obdélníku a do kolonky pro zadání barvy uvedeme tv.getBarva() (viz obr. 2.20). Obrázek 2.20 Zadání odkazu na objekt prostřednictvím volání metody Obdobně bychom postupovali i v případě, když bychom chtěli zadat barvu prostřednictvím zasláním zprávy Barva getBarva(názevBarvy) třídě Barva. Tato zpráva vyžaduje zadání parametru, kterým je název požadované barvy. A protože název barvy je text, musíme jej zadat v uvozovkách. Kdybychom proto chtěli tento způsob získání barvy použít při kreslení dalšího zeleného čtverce, zadali bychom barvu Barva.getBarva( "zelená" ); Okno konstruktoru, kterým žádáme o vytvoření tohoto čtverce (je stejný jako minulý, pouze je o 100 bodů níže), si můžete prohlédnout na obrázku 2.21. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 76 z 433 Kapitola 2: Pracujeme s třídami a objekty 77 Obrázek 2.21 Zadání odkazu voláním metody s parametrem 2.10 Shrnutí – co jsme se naučili Shrňme si, co jsme se v kapitole dozvěděli: OOP chápe okolní svět jako svět objektů, které sdružujeme do tříd. Každý objekt je instancí nějaké třídy. Termíny objekt a instance jsou synonyma. Objekty mezi sebou komunikují prostřednictvím zpráv. Objektově orientovaný program je v nějakém programovacím jazyce zapsaný popis tříd, jejich instancí a zpráv, které si objekty mezi sebou posílají. Reakci na zaslanou zprávu má na starosti speciální část programu, označovaná jako metoda. V jazyku Java vytváříme nové objekty zasláním zprávy s klíčovým slovem new následovaným názvem třídy a seznamem parametrů v kulatých závorkách. Tím se vyvolá tzv. konstruktor. Konstruktory jsou speciální metody zodpovědné za správné vytvoření instancí svých tříd. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 77 z 433 78 Myslíme objektově v jazyku Java 1.5 O vyhrazení paměťového místa pro vytvářený objekt a o jeho úklid poté, co přestaneme objekt potřebovat, se stará správce paměti. Zavoláním konstruktoru získáme odkaz na objekt. Program nikdy nepracuje s objektem, ale vždy pouze s odkazem na objekt. Odkazy, třídy, zprávy a další objekty, s nimiž v programu pracujeme, označujeme identifikátory. Identifikátory mohou obsahovat písmena (včetně písmen s diakritikou nebo např. japonských znaků), číslice a znaky „_“ (podtržítko) a „$“ (dolar). Nesmí začínat číslicí. V identifikátorech se rozlišují velká a malá písmena. Zprávy můžeme zasílat nejenom jednotlivým instancím, ale také celé třídě. Zasláním zprávy můžeme požadovat nejenom provedení nějaké akce, ale také např. vrácení hodnoty. Každá hodnota, kterou v programu použijeme, musí mít definován svůj datový typ. Datové typy dělíme na primitivní a objektové. Abychom si mohli vracené hodnoty prohlédnout, otevře BlueJ v reakci na zaslání zprávy požadující vrácení hodnoty dialogové okno, v němž vracenou hodnotu zobrazí. Vrací-li metoda hodnotu objektového typu, můžeme v dialogovém okně klepnout na příslušný záznam a pak požádat buď o prohlédnutí dané instance nebo o předání odkazu na ni. Při předání odkazu se tento uloží do zásobníku odkazů. Zprávy mohou mít parametry. Každý parametr má (stejně jako ostatní druhy dat) vždy definován svůj datový typ. Objekty si pamatují svůj stav prostřednictvím atributů. Vedle atributů instancí mohou být definovány i atributy třídy, které všechny instance dané třídy sdílí. V místní nabídce třídy můžeme BlueJ požádat o zobrazení atributů této třídy. Máme-li odkaz na instanci, můžeme BlueJ požádat o zobrazení jejích atributů a atributů její třídy. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 78 z 433 Kapitola 2: Pracujeme s třídami a objekty 79 Odkazy na instance, které jsou veřejnými atributy tříd, můžeme zadávat jako parametry i přímo zadáním názvu dané třídy následovaného tečkou a názvem příslušného atributu. Odkazy na instance objektových typů lze získávat i voláním metody vracející potřebný odkaz. Nově zavedené termíny (abecedně) Tato kapitola byla nabitá novými termíny. Abyste si je mohli zopakovat, tak jsem vám je tu vyjmenoval. Budete-li si chtít některý z nich připomenout, najděte si v rejstříku stránku, kde je poprvé použit. argument atributy instancí atributy třídy boolean členské proměnné datový typ, typ dceřiná třída diagram tříd false halda hlavička konstruktoru hlavička metody identifikátor instance int (typ) konstruktor metoda metoda třídy návratová hodnota new (operátor) parametr správce paměti stav objektu String (typ) true vnitřní proměnné výstupní hodnota metody @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 79 z 433 80 3. Myslíme objektově v jazyku Java 1.5 Vytváříme vlastní třídu Kapitola 3 Vytváříme vlastní třídu ☯ Co se v kapitole naučíme V minulé kapitole jsme si s objekty hráli a ukazovali si, jak co funguje. V této kapitole začneme opravdu programovat a napíšeme své první řádky. Nezačneme však od nuly, ale budeme rozšiřovat projekt, s nímž jsme se seznámili v minulé kapitole. Předem se omlouvám, že tato kapitola bude trochu delší, protože se v ní budeme postupně seznamovat se zápisem všech konstrukcí, které jsme si v minulé kapitole vysvětlili. Budeme se v ní proto věnovat prakticky pouze kódu, tj. tomu, jak to či ono zapsat. Od příští kapitoly bych chtěl již obě části výkladu vyvážit. Vždy si budeme chvíli vyprávět o třídách, objektech a jejich vlastnostech, a pak si ukážeme, jak to, co jsme se právě naučili, zapsat do programu. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 80 z 433 Kapitola 3: Vytváříme vlastní třídu 81 Pro práci s touto a příští kapitolou si otevřete projekt 03_Třídy_A. Přípona A v názvu souboru značí, že se jedná o výchozí projekt dané lekce. Vedle něj budete potřebovat ještě projekt 03_Třídy_Z, který oproti projektu 03_Třídy_A obsahuje třídy, které přidáme nebo vytvoříme v průběhu třetí a čtvrté kapitoly. Kromě toho v něm najdete zdrojový kód třídy Strom, v různých fázích rozpracovanosti tak, jak ji budeme v této kapitole postupně tvořit. Jednotlivé etapy přípravy jsou definovány jako třídy s názvem zakončeným číslem kapitoly a písmenem. Ve třetí kapitole tak vzniknou postupně třídy Strom_3a, Strom_3b a Strom_3c, v dalších kapitolách přidáme další. Nový projekt má oproti minulému trochu přeuspořádané třídy v diagramu tříd, aby se v něm lépe zobrazily vazby k třídám, které v průběhu této lekce sami nadefinujete. Kromě toho v něm přibyla pomocná třída P obsahující některé pomocné metody, které se nám budou hodit. Jak jsem sliboval v první kapitole, uděláme drobnou terminologickou změnu. Java realizuje poslání zprávy prostřednictvím zavolání odpovídající speciální funkce, které říkáme metoda. Od této chvíle proto budu velmi často používat místo termínu posílání zpráv termín volání metod. Aby těch úvodních poznámek nebylo málo, přidám ještě jednu. V průběhu dalšího textu budu velmi často říkat, že něco definujeme nebo deklarujeme. Mezi těmito termíny je drobný rozdíl, který občas začátečníkům uniká: Budu-li říkat, že něco deklaruji, znamená to, že někde veřejně vyhlašuji, jaké má dané něco vlastnosti nebo se zavazuji, že splním to, co dané něco požaduje. Řeknu-li naopak, že něco definuji, znamená to, že dané něco v programu zavádím, nechávám tomu přidělit paměť, přiřadit počáteční hodnotu apod. Ještě se k této problematice v některých speciálních případech vrátím. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 81 z 433 82 3.1 Myslíme objektově v jazyku Java 1.5 První vlastní třída Říkali jsme si, že základem všeho jsou objekty a ty že jsou instancemi svých tříd. Abychom mohli vytvořit objekt, musíme mít nejprve definovanou třídu, jejíž instancí objekt bude. Zkusíme si proto vytvořit novou, prázdnou třídu. Postup je jednoduchý: 1. Stiskněte vlevo tlačítko Nová třída (viz obr. 3.1).(V tuto chvíli je jedno, jsou-li ostatní třídy přeloženy nebo ne.) Obrázek 3.1 Vytvoření nové třídy 2. V následně otevřeném dialogovém okně zadejte název třídy, který musí odpovídat pravidlům pro tvorbu identifikátorů. Protože chceme vytvořit prázdnou třídu, nazvěte ji příhodně Prázdná. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 82 z 433 Kapitola 3: Vytváříme vlastní třídu 83 Obrázek 3.2 Zadání názvu vytvářené třídy 3. Přepněte přepínač pod vstupním polem do poslední polohy nazvané Prázdná třída a své zadání potvrďte. BlueJ pak přidá právě vytvořenou třídu do diagramu tříd. Obrázek 3.3 Přidání nové třídy do diagramu tříd 4. Podívejte se na disk do složky, ve které máte uloženy soubory projektu, a přesvědčte se, že se zde objevil nový soubor nazvaný Prázdná.java. Tento soubor označujeme jako zdrojový soubor a budete do něj za chvíli zapisovat program, v němž budete definovat požadované chování třídy a jejích instancí. Zdrojový soubor tříd, které budeme v prostředí BlueJ vytvářet, se bude vždy jmenovat stejně jako vytvářená třída (a to včetně velikosti jednotlivých znaků) a bude mít příponu java. Pokud byste chtěli vytvářet zdrojové soubory jinde a jinak, musíte tuto konvenci dodržet. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 83 z 433 84 Myslíme objektově v jazyku Java 1.5 V diagramu tříd je obdélník představující novou třídu prozatím vyšrafován, ale my již víme, že šrafování znamená jen to, že třída ještě není přeložena. Stiskněte proto tlačítko Přeložit a BlueJ vám během chvilky třídu přeloží a připraví k použití. Nyní již můžeme s nově vytvořenou třídou pracovat. Protože jsme ji však prozatím nic nenaučili, nemáme ji z čeho zkoušet. Můžeme sice vytvořit její instanci, ale ta nebude nic smysluplného umět. Nebudeme tedy nad ní dlouze bádat a podíváme se jí pěkně na zoubek. 3.2 Zdrojový kód třídy Vybavení třídy potřebnými schopnostmi je na nás. Má-li třída a její instance něco umět, musíme to nejprve zapsat do jejího zdrojového kódu, který je uložen ve zdrojovém souboru, což je textový soubor, jenž se automaticky vytvořil při vytvoření třídy (v našem případě to byl soubor Prádná.java). Abychom do tohoto souboru mohli něco zapsat, musíme jej nejprve otevřít. Můžeme toho dosáhnout dvěma způsoby: poklepáním na třídu v diagramu tříd, zadáním příkazu Otevřít v editoru v místní nabídce třídy (viz obr. 3.4). Obrázek 3.4 Příkaz o otevření zdrojového kódu třídy v editoru Vyberte si postup, který je vám sympatičtější a otevřete editor se zdrojovým kódem třídy Prázdná. Text, který uvidíte (viz obr. 3.5), vygeneroval BlueJ v okamžiku, kdy jste jej požádali o vytvoření nové třídy. Je to téměř prázdná definice třídy, do které budete vpisovat váš program. Jinými slovy: BlueJ za vás napsal to, co byste stejně museli napsat. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 84 z 433 Kapitola 3: Vytváříme vlastní třídu 85 Obrázek 3.5 Okno zabudovaného editoru Prázdná třída To, co BlueJ připravil, je nejjednodušší možná definice třídy, která vypadá následovně: public class Prázdná {} Vysvětleme si nyní význam jednotlivých částí této definice: public Klíčové slovo public oznamuje, že třída je veřejná a že s ní proto může pracovat kdokoliv. Teoreticky je sice možné definovat třídu i bez použití klíčového slova public, ale tuto možnost nyní využívat nebudeme a situace, kdy to může být vhodné, si probereme až o několik kapitol později. class Klíčové slovo class oznamuje, že za ním následuje definice třídy. Časem se naučíme ještě jiná slova ohlašující definici třídy. Prázdná Za klíčovém slovem class následuje název neboli identifikátor třídy – musí proto splňovat příslušná pravidla (viz pasáž Pravidla pro tvorbu identifikátorů v jazyce Java na straně 46). Kromě toho musí platit, že název veřejné třídy musí být totožný s názvem souboru, v němž je uložen její zdrojový kód, a to včetně velikosti jednotlivých znaků. (Z toho logicky vyplývá, že v jednom souboru může být zdrojový kód nejvýše jedné veřejné třídy.) Soubor se zdrojovým kódem musí mít příponu java. { } Všechny doposud probrané části tvoří hlavičku definice třídy, podle které překladač pozná její základní charakteristiky. Za hlavičkou následuje tělo definice třídy uzavřené ve složených závorkách. Protože je naše definice dosud prázdná, je prázdný i obsah závorek. S výjimkou dvou přesně specifikovaných příkazů (budeme o nich mluvit později) musí být v jazyku Java vše ostatní, tj. atributy i metody, definováno v těle třídy. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 85 z 433 86 Myslíme objektově v jazyku Java 1.5 V rozebírané definici je dvojice složených závorek na stejném řádku jako definice třídy, kdežto v okně editoru je každá závorka na samostatném řádku. Java na uspořádání textu do řádků nehledí. Konec řádku pro ni má stejný význam jako mezera. Programy proto uspořádáváme tak, abychom se v nich co nejlépe vyznali. Poté, co zadáte výše popsanou minimální definici třídy, stiskněte tlačítko Přeložit (je úplně vlevo – viz obr. 3.6). BlueJ se „zamyslí“ a pokud jste neudělali chybu, tak dole pod editovaným textem napíše: Třída byla úspěšně přeložena – žádné syntaktické chyby Obrázek 3.6 Po úspěšném překladu vypíše editor zprávu Tím jste dokončili svoji první definici třídy. Můžete se nyní vrátit do okna projektu a vyzkoušet si, že vaše nová třída doopravdy funguje a je např. schopna vytvářet nové instance. Současně se také můžete podívat na disk a přesvědčit se, že se ve složce s projektem objevil vedle souboru Prázdná.java (v tom je, jak víte, vámi napsaný zdrojový kód) ještě soubor Prázdná.class, v němž je uložen přeložený bajtkód. Kromě toho zde naleznete ještě soubor Prázdná.ctxt, ale to je pomocný soubor prostředí BlueJ, který pro svoji práci nepotřebujete (pokud jej smažete, BlueJ si jej při příštím překladu dané třídy vytvoří znovu). Soubor s příponou class, v němž je uložený přeložený bajtkód, bývá označován jako class-soubor příslušné třídy. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 86 z 433 Kapitola 3: Vytváříme vlastní třídu ☺ ☺ 87 Kdybyste chtěli svůj projekt stěhovat na jiný počítač, stačí, když na něj přenesete pouze soubory s příponou java. Budete-li chtít, aby v přeneseném projektu zůstalo zachováno také uspořádání tříd v diagramu tříd, musíte přenést také soubor bluej.pkg. Ostatní soubory si BlueJ bez problémů a bez zdržování vytvoří, takže se s nimi přenášet nemusíte. Zdrojový soubor je obyčejný textový soubor. Pokud byste jej otevřeli v libovolném textovém editoru (např. v programu Poznámkový blok), zjistíte, že obsahuje pouze zadaný text, který můžete v tomto editoru klidně upravovat. Jenom dejte pozor na to, abyste jej neupravovali v době, kdy máte v prostředí BlueJ otevřen projekt, do nějž program patří. To by se vám editor a BlueJ pohádaly. Obrázek 3.7 Přeložená prázdná třída je funkční Implicitní konstruktor V minulé kapitole jsme si říkali, že vytvoření nové instance má na starosti speciální funkce (metoda), které se říká konstruktor. My jsme však žádnou takovou metodu nevytvořili, tak kde se vzala? @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 87 z 433 88 Myslíme objektově v jazyku Java 1.5 Konstruktor musí mít každá třída. Pokud proto programátor pro třídu žádný konstruktor nedefinuje, domyslí si překladač, že mu bude stačit ten nejjednodušší a definuje jej pro ni sám. Tento konstruktor označujeme jako implicitní. Jakmile programátor definuje jakýkoliv vlastní konstruktor, překladač se již nenamáhá a implicitní konstruktor nepřidává. Jinými slovy: jakmile programátor definuje ve třídě první konstruktor, musí je již definovat všechny, protože překladač již za něj nic nedoplní. 3.3 Odstranění třídy Prázdná třída, kterou jsme před chvílí vytvořili, může sloužit opravdu jen k výukovým účelům. V praxi bychom pro ni asi žádné uplatnění nenašli. Využijeme ji proto ještě k tomu, abychom si na ni ukázali, jak je možno třídu z diagramu tříd odstranit. Postup je jednoduchý: v místní nabídce rušené třídy zadáte příkaz Odstranit. Protože odstranění třídy je přece jenom operace, která má na celý projekt zásadnější dopad, tak se vás BlueJ pro jistotu nejprve zeptá, jestli to s tím odstraněním třídy myslíte vážně. Jakmile svůj úmysl potvrdíte, odstraní nejenom zdrojový soubor třídy, ale i všechny zmínky o tom, že třída byla někdy součástí projektu. Obrázek 3.8 Před odstraněním třídy vyžaduje BlueJ potvrzení Možná vás překvapí, že po žádosti o odstranění třídy sice třída z diagramu tříd zmizí, avšak její instance v zásobníku odkazů zůstane. Je to proto, že žádost o odstranění třídy je vlastně žádostí o odstranění příslušných souborů z disku. Avšak od chvíle, kdy jste vytvořili instanci třídy, má počítač všechny potřebné informace také v operační paměti, odkud je odstraníte pouze tak, že restartujete virtuální stroj. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 88 z 433 Kapitola 3: Vytváříme vlastní třídu 3.4 89 Přejmenování třídy Zkuste si vytvořit prázdnou třídu ještě jednou. Je opravdu jednoduchá, takže by vám to nemělo dělat žádný problém. Nyní začneme třídu vylepšovat a definujeme ji tak, aby její instance vytvářely na plátně nějaký obrázek. Pro začátek začneme s něčím opravdu jednoduchým – vytvořme např. třídu Strom, jejíž instance nakreslí na plátno symbolický listnatý strom s korunou a kmenem. Začneme tím, že třídu přejmenujeme. BlueJ nám v tom pomůže. Změníme-li totiž v souboru, v němž je uložen zdrojový kód, název veřejné třídy, změní BlueJ při ukládání tohoto souboru potřebným způsobem i název souboru. Můžete si vše hned vyzkoušet: změňte název třídy Prázdná na Strom. Nemusíte ji ani překládat – k tomu, aby BlueJ zareagoval, bohatě stačí třídu uložit (stisknete CTRL+S nebo v editoru zadáte příkaz Třída → Uložit). BlueJ pak okamžitě přejmenuje příslušnou třídu v diagramu tříd a jak se můžete přesvědčit i pomocí nějakého správce souborů, změní příslušně i název zdrojového souboru na disku a smaže případné související soubory navázané na původní název. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 89 z 433 90 Myslíme objektově v jazyku Java 1.5 Obrázek 3.9 Po uložení souboru s přejmenovanou třídou BlueJ tuto třídu v diagramu tříd okamžitě přejmenuje Mezi programátory v Javě se ustálila konvence, podle které se názvy tříd definují vždy s velkým počátečním písmenem a používají tzv. velbloudí notaci, při níž každé slovo několikaslovného názvu začíná vždy velkým písmenem – např. NázevTřídyZNěkolikaSlov. Restartování virtuálního stroje naštěstí neznamená restartování celého počítače (většinou☺) – je to poměrně jednoduchá operace. Stačí zadat klávesovou zkratku CTRL+SHIFT+R, nebo (pokud si ji např. nepamatujete) vyvolat místní nabídku Indikátoru činnosti (to je ta „pruhovaná tyčka“ nad levým okrajem zásobníku odkazů) a v ní zadat příkaz Restartovat VM (viz obr. 3.10). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 90 z 433 Kapitola 3: Vytváříme vlastní třídu 91 Obrázek 3.10 Restartování virtuálního stroje z místní nabídky Ukazatele činnosti 3.5 Bezparametrický konstruktor Takže skončíme již s prázdnou třídou a pokusíme se ji naučit něco užitečného. Nejprve bychom měli začít tím, že ji naučíme vytvářet smysluplné instance. K tomu potřebujeme definovat konstruktor. Pusťme se tedy do toho. Nejprve si musíme prozradit, jak vypadá definice konstruktoru. Pro jednoduchost začneme konstruktorem bezparametrickým. Definice třídy s prázdným bezparametrickým konstruktorem bude vypadat následovně: 1 public class Strom 2 { public Strom() 3 4 { } 5 6 } Projděme si nyní uvedený zdrojový kód a podívejme se znovu na význam a umístění jednotlivých částí řádek za řádkem: 1. Hlavička třídy, kterou již známe. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 91 z 433 92 Myslíme objektově v jazyku Java 1.5 2. Otevírací složená závorka označuje začátek těla třídy. Budeme ji psát na samostatný řádek za hlavičku. Všechny texty uvnitř těla třídy budeme pro přehlednost odsazovat. Velikost odsazení bývá většinou nastavena na 2 až 4 znaky – já budu používat 4 znaky, ale vy si můžete nastavit odsazení vlastní. 3. Hlavička konstruktoru. Vidíte, že se podobá hlavičce třídy. I tato hlavička je uvozena modifikátorem public, kterým oznamujeme, že s konstruktorem může pracovat každý. Za modifikátorem public následuje jméno třídy, které je zároveň názvem konstruktoru. Za názvem konstruktoru následuje seznam parametrů v závorkách. U bezparametrického konstruktoru je tento seznam prázdný. 4. Tělo konstruktoru je (stejně jako tělo třídy) uzavřeno ve složených závorkách. Stejně jako u tříd budeme otevírací složenou závorku psán na samostatný řádek. Stejně jako u těla třídy budeme vnitřek těla konstruktoru odsazovat. 5. Konec těla konstruktoru. Závorku uzavírající tělo konstruktoru budeme psát na samostatný řádek. Uzavírací závorku budeme zarovnávat pod otevírací závorku těla konstruktoru. 6. Konec těla třídy. Stejně jako u konstruktoru i u třídy budeme její uzavírací závorku psát na samostatný řádek. Uzavírací závorku těla třídy budeme zarovnávat pod příslušnou otevírací závorku. Na konci minulé podkapitoly jsme si řekli, že instance čerstvě přejmenované třídy Strom nakreslí na plátno jednoduchý listnatý strom s korunou a kmenem. Kdybychom jej měli nakreslit „ručně“, asi bychom vytvořili zelenou instanci elipsy, kterou bychom použili jako korunu, a doplnili ji dole červeným (hnědá v seznamu není) obdélníkem, který by představoval kmen. (Zkuste si to!) Při tvorbě programu budeme postupovat naprosto stejně, pouze místo zadávání příkazů z nabídky tyto příkazy napíšeme do těla konstruktoru. Naše první smysluplná definice třídy tedy může vypadat např. následovně: @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 92 z 433 Kapitola 3: Vytváříme vlastní třídu 93 1 public class Strom 2 { public Strom() 3 4 { 5 new Elipsa( 0, 0, 100, 100, Barva.ZELENÁ ); 6 new Obdélník( 45, 100, 10, 50, Barva.ČERVENÁ ); 7 } 8 } Co jsme udělali? V těle konstruktoru jsme zadali dva příkazy. Na pátém řádku programu jsme zavolali konstruktor elipsy, kterému jsme předali následujících 5 parametrů: vodorovnou a svislou souřadnici jsme nastavili na 0, šířku a výšku jsme nastavili na 100, barvu jsme definovali prostřednictvím předání odkazu na atribut ZELENÁ třídy Barva. Obdobně jsme posupovali i na šestém řádku v případě obdélníka, který představuje kmen. Zde jsme si pouze museli nejprve spočítat vodorovnou souřadnici kmene, aby byl umístěn přesně pod středem koruny. Má-li mít kmen např. šířku 10 bodů, musí být umístěn 5 bodů před tento střed – a to jsme udělali. Zkušenější programátoři často umisťují otevírací závorku těla třídy, konstruktoru atd. na konec předchozího řádku. Ušetří tím jeden řádek, ale ztíží tím kontrolu toho, že každá uzavírací závorka má svoji odpovídající otevírací závorku a naopak. Vřele vám proto doporučuji, abyste alespoň zpočátku určitě umisťovali jak otevírací, tak uzavírací závorku na samostatné řádky. Vyhnete se tak řadě „oblíbených“ chyb. Všimněte si, že každý příkaz je ukončen středníkem. Na to musíte dát pozor. Zapomenutý středník patří k jedněm z nejčastějších začátečnických chyb (naštěstí je to chyba lehce odhalitelná a odstranitelná). Na druhou stranu vám prozradím, že Java (na rozdíl např. od jazyku Visual Basic) nijak nelpí na tom, na kolika řádcích bude příkaz rozprostřen nebo jestli umístíte několik příkazů na jeden řádek. Při psaní programu se proto snažte uspořádat jednotlivé příkazy tak, aby byl program co nejpřehlednější. K této otázce se ještě několikrát vrátím. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 93 z 433 94 Myslíme objektově v jazyku Java 1.5 Java nerozlišuje tzv. bílé znaky (mezera, tabulátor, konec řádku, konec stránky). Kdekoliv může být v programu mezera, tam může být kterýkoliv jiný bílý znak – např. právě konec řádku. Přeložte nyní upravený program a podívejte se na diagram tříd. BlueJ zaregistroval, že jsme v těle třídy použili třídy Obdélník a Elipsa a okamžitě k nim natáhl šipky závislostí (viz obr. 3.11). Obrázek 3.11 BlueJ odhalil používání tříd a natáhl automaticky šipky závislostí Požádejte nyní v diagramu tříd třídu Strom, aby vytvořila novou instanci stromu. Po jejím vytvoření bude plátno vypadat podle obr. 3.12. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 94 z 433 Kapitola 3: Vytváříme vlastní třídu 95 Obrázek 3.12 Plátno se stromem 3.6 Ladění Ne vždy se nám podaří napsat program bez chyby. Pravdou je spíše opačné tvrzení – téměř každý program obsahuje nějakou chybu. Chyby, které se v programech objevují, bychom mohli rozdělit do tří skupin: Syntaktické chyby jsou prohřešky proti pravidlům zápisu jazyka. Tyto chyby jsou svým způsobem „nejpříjemnější“, protože je odhalí již překladač a poměrně jasně nás na ně upozorní. Syntaktickou chybou by v našich prvních programech bylo např. vynechání některé ze závorek, případně přidání nadbytečné závorky, vynechání středníku, zkomolení názvu volaného konstruktoru, atributu či třídy apod. Běhové chyby jsou chyby, na které se nepodaří přijít během překladu a které se projeví až za běhu programu a vedou k nějakým výjimečným situacím. Mezi takovéto chyby patří např. dělení nulou, pokus o použití objektu, který ještě nebyl vytvořen apod. Tyto chyby jsou sice nepříjemné, ale při důkladném testování programu bychom je měli mít šanci všechny (nebo alespoň skoro všechny) odhalit a opravit. Logické chyby (často se setkáte s označením sémantické1 chyby) jsou chyby v logice programu, které zapříčiní, že program nedělá přesně to, co má. Ty se 1 Sémantický = významový. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 95 z 433 96 Myslíme objektově v jazyku Java 1.5 umějí nejlépe maskovat a dá většinou nejvíce práce je odhalit a opravit. Sémantickou chybou by v našem předchozím programu např. bylo, kdybychom špatně spočítali souřadnice nebo velikost obrazců a výsledný strom by pak vypadal divně, pokud by vůbec vypadal jako strom. Vyzkoušíme si nyní všechny druhy chyb a podíváme se, jak na ně bude počítač (a následně i my) reagovat. Syntaktické chyby Jak jsem již řekl, odhalování a opravování syntaktických chyb je nejjednodušší (i když i ony nám jsou schopny občas připravit pěkný rébus), takže s jejich zkoušením začneme. Udělejte nejprve v programu chybu – odstraňte např. středník za prvním příkazem – a zkuste program znovu přeložit. BlueJ váš program opět nejprve uloží a pak jej začne překládat. Protože však najde v programu chybu, překlad nedokončí a místo očekávané hlášky Třída byla úspěšně přeložena – žádné syntaktické chyby zobrazí v dolním informačním poli chybovou zprávu ';' expected (viz obr. 3.13). Navíc ve zdrojovém textu zvýrazní řádek, v němž chybu odhalil. Obrázek 3.13 BlueJ ohlásil chybu při překladu Tato zpráva je sice psána anglicky, ale klepnutím na otazník na pravém okraji informačního pole otevřete dialogové okno, které vám většinou chybové hlášení přeloží a často i česky vysvětlí – viz obr. 3.14. Tato vysvětlení sice nejsou k dispozici pro všechny chyby, nicméně nejčastější chyby takto vysvětleny jsou. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 96 z 433 Kapitola 3: Vytváříme vlastní třídu 97 Obrázek 3.14 Pro většinu chyb má BlueJ připraveno české vysvětlení Připravte se na to, že na řadu chyb počítač přijde na jiném místě programu, než na kterém k chybě došlo. Chybu totiž neoznámí v místě, kde jsme ji udělali, ale v místě, kde ji odhalil. V našem předchozím příkladě mu samozřejmě nevadilo, že jsme středník neudělali hned za voláním konstruktoru. Jak jsem již řekl, Javě je poměrně jedno, jak program uspořádáme do řádků. Program proto chybění středníku objeví až v okamžiku, kdy narazí na další operátor new aniž by byl předchozí příkaz uzavřen středníkem – a k tomu dojde právě na následujícím řádku. Jakmile chybu opravíte, BlueJ váš program přeloží a budete jej moci znovu používat. Zkuste vyrobit další syntaktické chyby tak, že odstraníte nebo přidáte do programu nějaký znak a podívejte se, jak na ně bude BlueJ reagovat. Běhové chyby Běhové chyby jsou v pořadí nepříjemnosti chyb uprostřed. Překladač je sice neodhalí již při překladu, avšak chyba se „iniciativně“ projeví při běhu programu – většinou tím, že program zkolabuje. Abychom takovéto chyby odhalili, musí program krizovým místem proběhnout. Musíme si proto připravit sadu testů, které postupně prozkoumají všechny zákoutí programu a umožní nám takovéto chyby najít dřív, než na ně přijde zákazník. Podívejme se, jak v případě takovýchto chyb reaguje BlueJ. Zkuste např. v prvním příkazu nahradit první nulu výrazem 0/0 (v programech se pro dělení používá znak „/“) a požádejte opět o překlad. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 97 z 433 98 Myslíme objektově v jazyku Java 1.5 Lepší překladače by sice takovouto triviální chybu objevily již při překladu, ale BlueJ je program optimalizovaný pro výukové účely, takže se o takovéto fajnovosti nepokouší a program přeloží. Požádáte-li nyní třídu Strom o vytvoření instance, nepodaří se jí to. BlueJ proto otevře textový editor, zobrazí ve zdrojovém kódu místo, kde došlo k chybě, a ve spodním informačním poli vypíše, o jakou chybu se jedná. V našem případě zde napsal (viz obr. 3.15): ArithmeticException: / by zero Tuto zprávu bychom mohli přeložit tak, že vznikla výjimka při aritmetické operaci. Na druhém řádku je pak specifikováno, že se jedná o dělení nulou. Obrázek 3.15 Reakce prostředí na běhovou chybu Takováto chyba je však příliš primitivní a v programu byste ji asi neudělali. Zkusíme nějakou rafinovanější. Odstraňte z příkazu dělení nulou a zkuste v třetím parametru, v němž zadáváme požadovanou šířku obdélníku, nahradit původní hodnotu nulou. Příkaz pak bude mít podobu: new Elipsa( 0, 0, -3, 100, Barva.ZELENÁ ); Takováto podoba běhové chyby je daleko pravděpodobnější. Je nenápadná a k podobnému přehlédnutí může snadno dojít. Výsledek je však stejný: dojde k výjimečné situaci a program se zastaví v místě, kde k ní došlo (viz obr. 3.16). V uvedeném případě pak vypíše: IllegalArgumentException: Parametry nemají povolené hodnoty: x=0, y=0, šířka=-3, výška=0 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 98 z 433 Kapitola 3: Vytváříme vlastní třídu 99 Problém je v tom, že tentokrát k chybě dojde v konstruktoru elipsy, který odmítne vytvořit elipsu nulové šířky (to se ještě dá ze zprávy vyčíst). Vy však potřebujete vědět, kde chyba doopravdy vznikla. V našem jednoduchoučkém programu je to jasné, ale ve složitějších programech je to docela problém. Naštěstí Java nabízí prostředky, jak to zjistit. Jejich použití ale patří k vyšší škole programátorského umění, takže si o nich povíme až později. Obrázek 3.16 Konstruktor elipsy odmítá vytvořit instanci nulové šířky Logické (sémantické) chyby Základním problémem logických chyb je to, že si je nemusíme dlouho uvědomovat. Možná někteří z vás slyšeli o aféře s mikroprocesory Intel Pentium, které v jednom z triliónů případů ne zcela přesně vydělily dvě čísla. Nic nehavarovalo, jenom výsledek měl někde na desátém desetinném místě chybu. Na takovou chybu se dá přijít opravdu jen náhodou. V tom právě spočívá záludnost logických chyb. Uděláte-li v programu chybu, která je hned viditelná (např. umístíte-li špatně kmen vůči koruně stromu), je to v pohodě. Uděláte-li však chybu, která se navenek nijak výrazně neprojevuje, avšak o to citelněji škodí, je to obrovský problém. I logické chyby se snažíme odhalit sadou propracovaných testů, avšak u složitějších programů můžeme těžko zaručit, že jsme otestovali opravdu vše a že program žádné logické chyby neobsahuje. V dalším textu se vám budu snažit průběžně dávat tipy na to, jak logické chyby odhalovat, ale dopředu vám říkám, že pro jejich odhalení žádné zaručené pravidlo neexistuje. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 99 z 433 100 3.7 Myslíme objektově v jazyku Java 1.5 Konstruktor s parametry Doufám, že jsem vás předchozí podkapitolou o chybách moc nevyděsil a že můžeme pokračovat a dále vylepšovat náš první program. Strom jsme sice nakreslili, ale vytvoříme-li druhý, nebudeme jej moci od prvního odlišit, protože se budou překrývat. Vytvoříme proto druhý konstruktor, který bude mít parametry, jež nám umožní specifikovat, kam se má vytvářený strom nakreslit. Vzpomeneme si, že parametry se píší do kulatých závorek za název konstruktoru a že před každým parametrem musí být nejprve uveden jeho typ. Je-li parametrů více, oddělují se čárkou. Konstruktor umožňující nakreslit strom na zadané souřadnice by mohl vypadat následovně: 1 public Strom( int x, int y ) 2 { 3 new Elipsa ( x, y, 100, 100, Barva.ZELENÁ ); new Obdélník( x+45, y+100, 10, 50, Barva.ČERVENÁ ); 4 5 } Proberme si předchozí definici podrobněji: 1. V hlavičce konstruktoru se objevil neprázdný seznam parametrů. Všimněte si, že u každého parametru je uveden nejprve jeho typ a za ním identifikátor, jehož prostřednictvím se program na parametr v těle konstruktoru odvolává. 2. Otevírá tělo konstruktoru. 3. Tam, kde jsme v bezparametrickém konstruktoru dosazovali hodnoty vodorovné a svislé souřadnice konstruované elipsy, dosazujeme nyní hodnoty parametrů x a y. Zásada je taková, že kde chceme dosadit hodnotu parametru, tam vložíme jeho název. 4. V dalším příkazu jsme použití parametrů ještě vylepšili a dosadili jsme je přímo do výrazů. Jako vodorovnou souřadnici obdélníka dosadíme číslo, které je o 45 větší než hodnota parametru x a za svislou souřadnici číslo, které je o 100 větší než hodnota parametru y. 5. Konec těla konstruktoru. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 100 z 433 Kapitola 3: Vytváříme vlastní třídu ☺ 101 Pokud jste se v dřívějších dobách setkali s programováním, možná vás nutili používat pouze procedury a funkce, které jste před tím v programu definovali. Java na vás takovéto požadavky neklade. Na pořadí, v jakém definujete jednotlivé konstruktory jí nezáleží. Pořadí, v jakém definujete jednotlivé typy konstruktorů (a následně i atributů a metod), záleží zcela na vás. Přidejte výše uvedený kód do těla třídy vedle bezparametrického konstruktoru a třídu přeložte. Vraťte se pak do prostředí BlueJ a vytvořte na různých místech plátna několik instanci třídy Strom. Přesvědčte se, že se stromy nakreslily opravdu tak, jak bylo vaším záměrem. Vytvořte některou z tříd ČinkaO (O=Obdélník), ČinkaK (K=Kruh), Hvězda, Panáček, Panenka, jejichž instance se na plátně zobrazí obdobně, jako na obrázku: Konstruktor this Oba konstruktory, které jsme doposud vytvořili, jsou si velice podobné. To je u konstruktorů velmi časté, protože mají velice podobné úkoly. Programátoři jsou ale lidé líní (proto se také dali na programování) a neradi píší něco dvakrát. Navíc je opakované psaní stejného kódu i nebezpečné, protože když budeme chtít v tomto kódu později něco změnit, musíme si pamatovat, kde všude jsme jej použili, všechna místa navštívit a do kódu příslušnou změnu zanést. Stačí na jedno místo zapomenout nebo v něm opravu zanést trochu jinak a chybička je na světě. Java naštěstí nabízí způsob, jak se opakovanému psaní těla konstruktoru vyhnout – je jím použití klíčového slova this následovaného seznamem parametrů. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 101 z 433 102 Myslíme objektově v jazyku Java 1.5 Takto použité klíčové slovo this zastupuje jiný konstruktor téže třídy – který to je, to si překladač odvodí z počtu a typu zadaných parametrů. POZOR! Takovéto volání jiného konstruktoru musí být úplně prvním příkazem těla konstruktoru. Před ním smí být již pouze mezera nebo komentář. Využijeme-li klíčové slovo this, mohla by definice třídy Strom vypadat např. následovně: 1 public class Strom 2 { 3 public Strom() 4 { 5 this( 0, 0 ); 6 } 7 8 public Strom( int x, int y ) 9 { 10 new Elipsa ( x, y, 100, 100, Barva.ZELENÁ ); 11 new Obdélník( x+45, y+100, 10, 50, Barva.ČERVENÁ ); 12 } 13 14 } Vyzkoušejte si, že i po této změně definice bezparametrického konstruktoru vše pracuje podle očekávání. Zkusme nyní naši definici ještě vylepšit a definovat konstruktor, který by umožňoval zadat i výšku a šířku konstruovaného stromu. U tohoto stromu se budeme snažit dodržet pravidlo, že výška kmene je polovina výšky koruny a šířka kmene je zhruba desetina šířky koruny. Tím se nám sice definice trochu zkomplikuje, ale na druhou stranu získáme obecnou definici, která dokáže vytvářet stromy libovolné velikosti a v libovolné pozici. Protože bychom po prostém přidání této definice měli opět zdvojený kód, tak v konstruktoru se souřadnicemi opět použijeme volání konstruktoru s využitím klíčového slova this. Máme-li definovat konstruktor, který dokáže nastavit i výšku a šířku stromu, musíme vědět, jak se v programu zadává násobení a dělení. Prozradím vám, že naprostá většina programovacích jazyků používá pro násobení symbol „*“ (hvězdička) a pro dělení symbol „/“ (lomítko). Nejinak je tomu v Javě. Abychom měli přehled aritmetických operátorů kompletní, tak vám prozradím, že Java zavádí ještě operátor %, který vrací zbytek po dělení svých dvou operandů (např. 5 % 3 je 2). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 102 z 433 Kapitola 3: Vytváříme vlastní třídu 103 Výsledný kód vypadá následovně: 1 public class Strom 2 { public Strom() 3 4 { this( 0, 0 ); 5 } 6 7 public Strom( int x, int y ) 8 9 { this( x, y, 100, 150 ); 10 11 } 12 13 public Strom( int x, int y, int šířka, int výška ) { 14 new Elipsa ( x, y, šířka, 2*výška/3, Barva.ZELENÁ ); 15 16 new Obdélník( x+9*šířka/20, y+2*výška/3, šířka/10, výška/3, Barva.ČERVENÁ ); 17 18 } 19 20 } Při seznamování s příkazy jsem vám říkal, že Java nelpí na tom, aby byl příkaz na jednom řádku. V předchozím programu jsem toho využil a příkaz na řádku 16 bezostyšně zalomil na dva řádky, protože se mi na jeden řádek nevešel. V programátorském světě bývá dobrým zvykem nedělat řádky delší než 80 znaků. Budu se této zásady držet i v této učebnici. Prvé dva konstruktory v programu samy nic nedefinují a nechávají za sebe pracovat jiný konstruktor, kterému pouze předají vhodné hodnoty parametrů. Všimněte si, že prvý konstruktor pověřuje druhý a druhý pověřuje třetí. Při zavolání bezparametrického konstruktoru se proto nejprve zavolá druhý konstruktor, kterému se předají nulové souřadnice a ten místo sebe zavolá třetí konstruktor, kterému předá převzaté souřadnice a přidá k nim informace o požadované implicitní velikosti stromu. S takovýmto řešením se v programech setkáte často – je to vlastně dotažená zásada, že se ve zdrojovém kódu nemá pokud možno žádná část programu opakovat. V prvém konstruktoru jsou proto definovány implicitní souřadnice a v druhém pak implicitní velikost. Kdybychom hned v prvém konstruktoru volali třetí konstruktor, museli bychom uvést implicitní souřadnice i zde. Když bychom se je pak později rozhodli změnit, museli bychom si pamatovat, že jsou na dvou místech a na obou místech je pak také opravit. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 103 z 433 104 Myslíme objektově v jazyku Java 1.5 Opět platí: v takto jednoduchém programu byste to asi nepřehlédli (i když kdo ví…), ale ve složitějších programech by už bylo nebezpečí přehlédnutí veliké. Znovu proto připomínám: Naučte se psát i jednoduché programy podle zásad, které je třeba dodržovat při psaní programů složitých. Za prvé se zlozvyky velice těžko odnaučují a kromě toho nikdy nevíte, jak vám váš program v průběhu doby naroste. Definujte výše popsané obecnější konstruktory s parametry i pro svoji třídu, kterou jste vytvořili podle úkolu na konci podkapitoly o bezparametrickém konstruktoru. 3.8 Testování V kapitole o chybách jsme si říkali, že chceme-li minimalizovat počet chyb ve výsledném programu, musíme pro něj pečlivě připravit sadu testů, kterým program před odevzdáním podrobíme. TDD – vývoj řízený testy Moderní programování jde ale ještě dál. V současné době se stále více prosazuje myšlenka definovat nejprve sadu testů a teprve pak psát testovaný program. Při psaní programu se pak stačí soustředit pouze na to, aby výsledný program prošel napsanými testy. Jakmile program testy projde, programátor se zamyslí, jak by jej měl dále vylepšit, zase napíše testy a opět se snaží upravit program tak, aby testy prošel. Tato metodika již získala své jméno: Test Driven Development – vývoj řízený testy. Používá se pro ni zkratka TDD1. Budu-li se na ni chtít v textu někdy odvolat, tak tuto zkratku použiji. Postup doporučovaný TDD je možná na první pohled nesmyslný, ale při bližším zkoumání zjistíte, že má řadu výhod: V průběhu psaní testů si programátor ujasní, co přesně má vytvářený program dělat. 1 O této metodice vyšla velice zajímavá knížka Beck K.: Test-Driven Development By Example, Addison Wesley, 2003. Český překlad Vývoj programů řízený testy, Grada, 2004 (ISBN 80-247-). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 104 z 433 Kapitola 3: Vytváříme vlastní třídu 105 Při psaní programu může kdykoliv spustit předpřipravenou sadu testů a průběžně na nich kontrolovat, kolik jich už proběhlo a kolik práce mu ještě zbývá. Ve chvíli, kdy proběhnou všechny testy, je programátor s prací hotov a nemusí již přemýšlet nad tím, jestli v jeho programu nejsou chyby. Zkušenost ukazuje, že máte-li připravenou sadu testů před začátkem programování, je produktivita vaší práce výrazně vyšší – nejspíš proto, že jste klidnější, protože jste si jisti, že vám tyto testy kryjí záda a že můžete v každé situaci vyzkoušet, jestli se program po úpravě chová lépe nebo hůře. Budeme se proto této zásady snažit držet i v této učebnici. Bohužel, nebudu se jí moci držet pořád (i když bych rád), protože by rozsah knihy narostl do neúměrné velikosti. Jednou za čas si však vývoj části programu za pomoci této metodiky ukážeme. Autoři prostředí BlueJ jsou také přesvědčeni o důležitosti testů, a proto do programu přidali několik nástrojů, které jejich přípravu a spouštění zjednodušují. Tyto nástroje se stanou použitelnými po zaškrtnutí políčka Zobrazit nástroje pro testování v dialogovém okně pro nastavení vlastností programu (viz pasáž Konfigurace BlueJ na straně 411). Postupně se budeme učit je využívat. Testovací třída K testování využíváme speciální třídy, které jsou vybaveny některými zvláštními vlastnostmi. Budeme jim říkat testovací třídy. Abychom testovací třídy na první pohled odlišili od ostatních tříd, vybarvuje je BlueJ v diagramu tříd jinou barvou a označuje je tzv. stereotypem, což je popisek uzavřený ve «dvojitých lomených uvozovkách». Tento stereotyp obsahuje text unit test naznačující, že se jedná o test jednotky. O tom, co je to jednotka, přitom rozhoduje programátor – může to být třída, část třídy a nebo naopak celý projekt. Do testovací třídy pak umisťujeme všechny testy prověřující danou jednotku. Testovací třídu můžeme vytvořit např. tak, že v místní nabídce třídy, kterou chceme testovat, zadáme příkaz Vytvořit testovací třídu. BlueJ pak vytvoří novou třídu, jíž název složí z názvu testované třídy a slova Test, a umístí ji pod třídu, v jejíž místní nabídce jsme vytvoření testovací třídy zadali (voz obr. 3.17). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 105 z 433 106 Myslíme objektově v jazyku Java 1.5 Obrázek 3.17 Testovací třída třídy Strom Testovací třída se bude testované třídy držet jako klíště. Kamkoliv testovanou třídu posunete, posune se testovací třída za ní. Testovací třídu sice můžete přesunout na libovolné jiné místo diagramu tříd, ale jakmile pak hnete s testovanou třídou, testovací třída k ní opět přiskočí (vyzkoušejte si to). Přípravek Velmi často je výhodné provádět každý z testů se stejnou výchozí sadou objektů. K tomuto účelu nabízí BlueJ možnost definovat tzv. přípravek, což je sada objektů, které se vytvoří před spuštěním každého testu, doplněná případně o nastavení nějakých okrajových podmínek. Definice přípravku je jednoduchá: odkazy na všechny objekty, které chcete do přípravku umístit, nejprve připravíte do zásobníku odkazů a pak v místní nabídce příslušné testovací třídy zadáte příkaz Zásobník odkazů −> testovací přípravek. BlueJ pak všechny odkazy ze zásobníku odkazů odebere a umístí je do testovacího přípravku. Můžeme si to hned vyzkoušet. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 106 z 433 Kapitola 3: Vytváříme vlastní třídu 107 1. Požádejte třídu Strom, aby vytvořila následující objekty: strom1 vytvořený bezparametrickým konstruktorem, strom2 vytvořený na souřadnicích x=0, y=150, strom3 o šířce 100 a výšce 90 vytvořený na souřadnicích x=100, y=100. 2. Požádejte třídu StromTest, aby obsah zásobníku odkazů uložila jako přípravek. Třída si pak všechny odkazy zapamatuje a restartuje virtuální stroj (a tím zároveň smaže odkazy ze zásobníku odkazů). 3. Požádejte nyní třídu StromTest, aby naplnila zásobník odkazů z přípravku. Třída vytvoří všechny instance, na které byly před tím odkazy v zásobníku odkazů a do zásobníku umístí odkazy na tyto instance. Odkazy budou mít i stejná jména, jen jejich pořadí se možná bude lišit, ale na tom beztak nezáleží. Při tvorbě přípravku si musíte uvědomit, že BlueJ sleduje vaše chování od posledního restartu virtuálního stroje, tj. např. od chvíle, kdy jste naposledy vytvořili, resp. přeložili nějakou třídu. Budete-li si před vlastním vytvořením přípravku hrát a vytvářet a mazat různé objekty, BlueJ po žádosti o uložení obsahu zásobníku do přípravku uloží nejenom všechny vaše dosavadní hrátky. BlueJ se totiž nevytváří přípravek na základě toho, co najde v zásobníku odkazů, ale na základě toho, jaké zprávy jste komu poslali od chvíle, kdy jste restartovali virtuální stroj – to nastane např. při překladu libovolné třídy. Chcete-li proto mít jistotu, že sledování vaší činnosti začne právě teď, požádejte BlueJ o restartování virtuálního stroje (kdo zapomněl, jak se to dělá, tak se může podívat na obr. 3.10 na straně 91). Úprava obsahu přípravku Někdy se stane, že si dodatečně uvědomíme, že by se nám hodilo, aby přípravek vypadal trochu jinak. Není problém – stačí „nasypat“ obsah přípravku do zásobníku, přidat nebo odebrat příslušné instance a výslednou podobu zásobníku opět uložit do přípravku. Opět si to hned zkusíme: 1. Restartujte virtuální stroj. 2. Požádejte třídu StromTest o naplnění zásobníku odkazů. 3. Požádejte třídu Strom o vytvoření instance strom4 na souřadnicích x=200, y=0, o rozměrech šířka=100, výška=200. 4. Požádejte třídu StromTest o převedení zásobníku odkazů na přípravek. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 107 z 433 108 Myslíme objektově v jazyku Java 1.5 5. Třída otevře dialogové okno, v němž vás upozorní na to, že daný test již má deklarovaný přípravek a zeptá se vás, jestli jej chcete nahradit (viz obr. 3.18). Odpovězte stiskem tlačítka Nahradit. Obrázek 3.18 Svůj úmysl předefinování testovacího přípravku musíte potvrdit 6. Od této chvíle má třída deklarován nový přípravek, který můžete ihned vyzkoušet. Po jeho aktivaci by mělo plátno vypadat obdobně jako na obr. 3.19. Obrázek 3.19 Vzhled plátna generovaný upraveným testovacím přípravkem Výše popsaným způsobem má smysl upravovat testovací přípravek tehdy, chcete-li do něj zahrnout ještě nějaký další objekt. Není moudré se takto interaktivně pokoušet z přípravku nějaký objekt odstranit. V takovém případě je výhodnější otevřít zdrojový kód třídy a zařazení objektu do zásobníku odkazů změnit ručně. Ještě se k této problematice vrátíme, až toho budeme vědět dost na to, abychom si mohli prohlédnout zdrojový kód testovací třídy a ukázat si, jak jej můžeme sami upravovat. Prozatím se spokojíme s tím, že testovací přípravek je naším testem, který nám umožní ověřit, že náš program funguje správně. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 108 z 433 Kapitola 3: Vytváříme vlastní třídu 109 Vytvořte testovací třídy s přípravky i pro vaše vlastní třídy a vyzkoušejte, že fungují. 3.9 Deklarace atributů Nyní již tedy umíme umístit náš strom tam, kam potřebujeme, a umíme jej vytvořit tak velký, jak potřebujeme. Teď bychom jej ještě potřebovali rozhýbat. Kdybychom ale zůstali u dosavadních definic konstruktorů, tak bychom toho s našimi stromy moc dělat nemohli. Jejich koruna a kmen se sice nakreslí, ale pak s nimi nic dalšího dělat nemůžeme. Abychom je mohli ještě někdy o něco požádat (např. o to, aby se přesunuly), musíme si někde zapamatovat odkaz na ně. Cokoliv si instance potřebuje zapamatovat, ukládá do atributů. Můžete si je představit jako schránky, do nichž mohou některé metody něco uložit a jiné pak pracovat s tím, co je zde uloženo. Atributy deklarujeme skoro stejně jako parametry. Neuvádějí se však v žádné hlavičce, ale v těle třídy vně těl jejích konstruktorů a metod. To je totiž jediné místo, kde na ně mohou všechny metody dané třídy „vidět“, aby je mohly použít. Bývá dobrým zvykem nerozstrkat deklarace atributů po celé definici třídy, ale umístit je pohromadě buď na její začátek nebo na její konec. My je budeme umísťovat na počátek – je to častější a navíc mi to připadá i logičtější. Deklarace atributu sestává z následujících částí: 1. Nepovinné modifikátory, které blíže specifikují některé vlastnosti definovaného atributu. Za chvilku vám o nich povím více. 2. Typ atributu, tj. název primitivního typu nebo název třídy, na jejíž instanci atribut ukazuje. 3. Název atributu – podle konvencí začíná název atributu malým písmenem. U několikaslovných názvů se používá velbloudí notace. Název atributu by měl jasně označovat, jaká informace je v atributu uložena. 4. Nepovinné přiřazení počáteční hodnoty sestávající z rovnítka a hodnoty, kterou chceme, aby měl atribut před svým prvním použitím. 5. Závěrečný středník. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 109 z 433 110 Myslíme objektově v jazyku Java 1.5 Deklarace atributů je současně i jejich definicí (rozdíl mezi deklarací a definicí viz terminologická poznámka na str. 81.), protože se jim při té příležitosti vždy vyhradí místo v paměti a přiřadí počáteční hodnota. Atributy, kterým v deklaraci nepřiřadíte počáteční hodnotu vy, za vás překladač „vynuluje“. Nemůže se tedy stát, že byste v programu začali používat atribut, v němž by bylo nějaké nedefinované smetí. (Je-li v něm nějaké smetí, museli jste si je tam dát sami. ☺) Modifikátory přístupu Jak jsem před chvílí řekl, před vlastní deklaraci atributu můžeme přidat modifikátory. Nejpoužívanější z nich jsou modifikátory přístupu, které specifikují, kdo všechno smí s daným atributem pracovat (kdo k němu smí přistoupit). Své modifikátory přístupu mohou mít nejenom atributy, ale i konstruktory a celé třídy. Modifikátory přístupu definují přístupová práva k daným objektům (třídám, konstruktorům, atributům, …). Prozatím jsem se setkávali pouze s modifikátorem public, který oznamoval, že příslušnou třídu či konstruktor mohou používat všichni. Atributy jsou však považovány za soukromou věc každé třídy, ve které nemá nikdo cizí mít šanci se hrabat. Je-li třída ochotna některé informace o svých atributech zveřejnit, dělá to většinou jinými metodami, než uvolněním jejich přístupových práv. Vše, co chceme deklarovat jako soukromou věc třídy, označujeme modifikátorem private. Dohodněme se, že atributy budeme primárně označovat jako private. Jiná přístupová práva jim budeme přiřazovat pouze ve výjimečných případech a měli bychom mít tuto změnu vždy nějak zdůvodněnu. Jako soukromé můžeme označit nejenom atributy, ale také konstruktory a metody. Učiníme tak v případě, kdy je daná metoda definována jako pomocná a není žádný důvod pro to, aby ji ostatní třídy mohly používat. Vylepšujeme Strom Vraťme se nyní zpět k našemu stromu. Budeme-li jej chtít posunout, mohli bychom to udělat např. tak, že bychom o příslušný posun požádali jeho korunu (elipsa se přeci přesouvat umí) a poté kmen (obdélník se také umí přesouvat). Po- @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 110 z 433 Kapitola 3: Vytváříme vlastní třídu 111 kud by si naše instance pamatovala odkazy na korunu a kmen, mohla by jim pak poslat zprávu, ve které by je požádala o to, aby se posunuly. Doplníme proto náš program o atributy, v nichž si zapamatujeme odkaz na korunu a kmen, a při konstruování stromu do těchto atributů uložíme odkazy, které nám vracejí konstruktory. Upravená definice třídy bude vypadat následovně: 1 public class Strom 2 { 3 private Elipsa koruna; private Obdélník kmen; 4 5 6 7 public Strom() 8 { this( 0, 0 ); 9 10 } 11 12 public Strom( int x, int y ) { 13 14 this( x, y, 100, 150 ); 15 } 16 17 public Strom( int x, int y, int šířka, int výška ) { 18 19 koruna = new Elipsa ( x, y, šířka, 2*výška/3, Barva.ZELENÁ ); 20 kmen = new Obdélník( x+9*šířka/20, y+2*výška/3, 21 šířka/10, výška/3, Barva.ČERVENÁ ); 22 } 23 } Všimněte si, že jsem oba atributy umístil na začátek třídy (jak jsem sliboval). Dohodněme se dopředu, že naše třídy budou vždy začínat definicí atributů, pokračovat definicí konstruktorů a končit definicemi metod. Nyní třídu přeložíme a nahráním přípravku do zásobníku odkazů hned otestujeme, že jsme úpravou programu nic nepokazili. Požádejte nyní BlueJ o prohlédnutí kterékoliv z instancí v zásobníku. U každé se dozvíte, že má soukromý atribut koruna, který je typu Elipsa a soukromý atribut kmen typu Obdélník. Klepnete-li na některý z nich, zvýrazníte jej a po stisku tlačítka Prohlížet si můžete prohlédnout jeho útroby, tj. jeho atributy. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 111 z 433 112 Myslíme objektově v jazyku Java 1.5 Obrázek 3.20 Prohlížeč nám ukáže atributy stromu Doplňte o atributy i svoje třídy a vyzkoušejte je nahráním přípravku do zásobníku odkazů. Možné důsledky zveřejnění atributů Jak jsem řekl, všechny atributy by měly být deklarovány jako soukromé. Ukažme si, co by se mohlo stát, kdyby tomu tak nebylo: 1. Změňte deklaraci koruny a označte ji jako veřejnou. 2. Třídu znovu přeložte. 3. Vytvořte její instanci např. bezparametrickým konstruktorem. 4. Podívejte se na vytvořenou instanci prohlížečem. Oproti stavu, který prohlížeč ukazoval před „publikováním koruny“ je nyní „živé“ i tlačítko Získat odkaz indikující, že můžete získat odkaz na daný objekt a umístit jej např. v zásobníku odkazů. 5. Stiskněte tlačítko Získat odkaz a novému odkazu dejte název koruna. 6. V zásobníku odkazů požádejte instanci koruna např. o to, aby se posunula vpravo. 7. Koruna stromu, na jejíž instanci odkaz vede, se poslušně přesune požadovaným směrem. Můžete ji samozřejmě zadat jakýkoliv jiný příkaz (např. smaž) a ona poslechne. Předpokládám, že možné důsledky zveřejnění atributů jsou z tohoto příkladu jasné. Protože je atribut veřejný, může k němu přistupovat kdokoliv. Je úplně jedno, zda nevhodnou manipulací s atributem někdo poškodí vaši instanci záměrně nebo omylem – důsledkem bude poškozená instance. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 112 z 433 Kapitola 3: Vytváříme vlastní třídu 113 3.10 Syntaktické definice Tato kapitola trochu odbočuje od vlastního výkladu. Chtěl bych vás v ní seznámit se způsobem zápisu syntaktických definic, pomocí nichž lze jednoznačně popsat, jak má vysvětlovaná konstrukce vypadat. Možná bude některým z vás připadat těžká. Pokud se vám bude zdát, že je příliš abstraktní, klidně ji přeskočte a vraťte se k ní později, až si budete chtít ujasnit, jak si máte nějakou syntaktickou definici „přeložit“. Při popisu syntaxe programových konstrukcí (syntaxe = souhrn pravidel, jak konstrukci zapsat) nebývá slovní popis optimálním způsobem vyjádření, protože se při něm často ztrácejí cenné informace v záplavě okolních slov. V programátorském světě se proto používají jiné druhy popisu. Jedním z nich jsou syntaktické definice. Ty budu používat i já. V syntaktických definicích je třeba rozlišit, co se z nich má do programu „opsat“, co pouze zastupuje nějaký objekt a co je pouze pomocný vyjadřovací prostředek pro popis struktury dané konstrukce. V syntaktických definicích, které budu v tomto kurzu používat, uvedu vždy na prvním řádku tučně název popisované konstrukce a na dalších, odsazených řádcích pak popíšu vlastní definici. V ní budu používat následující prvky: tučně Pro ty části konstrukce, které se mají do výsledného programu opsat (např. pro složené závorky ohraničující tělo konstruktoru či třídy nebo pro čárku oddělující jednotlivé parametry v seznamu), budu používat podšeděné tučné neproporcionální písmo. název Pro názvy prvků, které se v definici vyskytují a které jsou samy definovány jinde, budu používat kurzivu. [] Hranaté závorky budou uzavírat volitelnou část konstrukce, tj. část, která se v konstrukci může, ale také nemusí vyskytovat. … Výpustka (trojtečka) bude následovat za prvkem, který se může v konstrukci vyskytovat opakovaně. {} Složené závorky budou uzavírat skupinu prvků, s nimiž budu chtít v definici pracovat jako s celkem – např. za ně budu chtít vložit výpustku naznačující, že celá skupina se může opakovat. ¦ Dvojitá svislá čára („svislítko“) bude mít funkci nebo, která říká, že ve výsledné konstrukci se může objevit buď prvek vlevo od něj anebo @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 113 z 433 114 Myslíme objektově v jazyku Java 1.5 prvek vpravo od něj. Ne však oba zároveň (to bychom museli zapsat jinak). Definici konstruktoru bychom podle těchto pravidel zapsali následovně: Konstruktor: [ modifikátor ]… název_třídy ( [ definice_parametru [, definice_parametru ]… ] ) { [ příkaz ]… } Definice_parametru: typ název Definici interpretujeme tak, že na počátku se může, ale nemusí vyskytovat modifikátor (zatím známe pouze modifikátor public, ale je jich víc). Modifikátorů zde dokonce může být uvedeno několik. Za modifikátorem následuje název třídy, jejíž instance konstruktor vytváří, a za ním pak otevírací kulatá závorka uvozující seznam případných parametrů. Za ní může, ale nemusí být uvedena definice použitého parametru. Za definicí prvního parametru mohou být i definice dalších parametrů, ale každá z nich již musí být od předchozí definice oddělena čárkou. Za seznamem parametrů napíšeme zavírací kulatou závorku a za ní otevírací složenou závorku uvozující tělo konstruktoru. V těle konstruktoru nemusí být nic, ale může tam být i několik příkazů. Tělo ukončí uzavírací složená závorka. V definici konstruktoru se vyskytuje prvek definice_parametru. Ten vytvoříme tak, že uvedeme typ parametru a za ním jeho název. Abychom si ukázali použití složených závorek a „svislítka“, rozebereme si ještě definici identifikátoru: Identifikátor: { písmeno ¦ _ ¦ $ } [ písmeno ¦ _ ¦ } ¦ číslice ]… Tato definice je však nešikovná a uvedl jsem ji pouze proto, abych v ní mohl použít i složené závorky. V praxi bychom identifikátor definovali asi následovně: Identifikátor: zobecněné_písmeno [ zobecněné_písmeno ¦ číslice ]… Zobecněné_písmeno: písmeno ¦ _ ¦ $ Uvedená definice říká, že identifikátor musí začínat zobecněným písmenem, za ním může následovat libovolný počet zobecněných písmen a číslic. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 114 z 433 Kapitola 3: Vytváříme vlastní třídu 115 3.11 Definujeme vlastní metodu Jak jsem již dříve naznačil, instance a třídy reagují na zasílané zprávy tak, že spustí speciální podprogram, kterému budeme říkat metoda. Metoda definuje reakci instance či třídy na příslušnou zprávu. Programátoři proto již většinou ani neříkají, že poslali třídě či instanci zprávu, ale říkají, že zavolali její metodu. My už jsme vlastně v předchozích podkapitolách vlastní metody definovali, protože konstruktor je také metoda, i když zvláštní. V této kapitole se však začneme věnovat metodám, které zvláštní nejsou. Budu-li v dalším textu hovořit o metodách, budu tím myslet pouze standardní metody a nikoliv konstruktory. Pokud bych náhodou hovořil o něčem, co je společné standardním metodám i konstruktorům, výslovně to uvedu. Podívejme se nejprve, jak by měla taková definice standardní metody správně vypadat. Zápis syntaxe definice metody vypadá následovně: Definice_metody: [ modifikátor ]… typ_návratové_hodnoty název ( [ definice_parametru [, definice_parametru ]… ] ) { [ příkaz ]… } Přeložme si nyní tuto syntaktickou definici do „srozumitelštiny“. Metoda má (stejně jako třída) hlavičku a tělo. Hlavička sestává z následujících částí: 1. Nepovinné modifikátory – prozatím jsme se seznámili s modifikátory přístupu public a private. Ty označují, kdo všechno smí metodu volat (tj. kdo smí poslat příslušnou zprávu). 2. Typ návratové hodnoty. Tento typ musíme uvést i tehdy, když metoda nic nevrací – v takovém případě uvádíme typ void (prázdno). 3. Název metody. Pro názvy metod se používají stejné konvence jako pro názvy atributů, tj. začínají malým písmenem, přičemž se u několikaslovných názvů používá velbloudí notace. 4. Seznam parametrů v kulatých závorkách. Pro tento seznam platí naprosto stejná pravidla jako pro seznam parametrů konstruktorů, tj.: U každého parametru musí být uveden nejprve jeho typ a za ním jméno, jehož prostřednictvím se budeme v těle metody odvolávat na hodnotu tohoto parametru. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 115 z 433 116 Myslíme objektově v jazyku Java 1.5 Je-li parametrů více, oddělují se jednotlivé parametry v seznamu čárkami. Nemá-li metoda žádné parametry, musí se za jménem uvést prázdné závorky. 5. Tělo metody uzavřené mezi dvojici složených závorek. Pro tělo metody platí: Tělo metody sestává ze seznamu příkazů, který může být i prázdný, tj. tělo nemusí obsahovat žádný příkaz. Jednotlivé příkazy není třeba nijak oddělovat. Překladač umí poznat, kde jeden příkaz končí a druhý začíná. Podle konvencí píšeme ohraničující závorky každou na samostatný řádek a zarovnáváme je pod první znak hlavičky. Podle konvencí odsazujeme příkazy uvnitř těla cyklu oproti hlavičce o dva až čtyři znaky. Od této chvíle již nebudu v textu při zmínkách o metodách uvádět typ návratové hodnoty a pokud nebude hrozit nedorozumění, tak ani seznam parametrů. Pouze v případě, kdy vás budu žádat, abyste z místní nabídky třídy v diagramu tříd nebo instance v zásobníku odkazů poslali třídě či instanci zprávu, budu uvádět plný text příslušné položky. První metodou, kterou bychom měli pro náš strom definovat, je metoda nakresli(), protože ji většina ostatních metod bude potřebovat. Tato metoda má za úkol překreslit náš strom (např. tehdy, když jej někdo jiný smaže). Její definice bude jednoduchá: nemá žádné parametry ani nic nevrací, takže bude mít jednoduchou hlavičku, a díky jednoduchosti naší instance i jednoduché tělo – prostě požádá instance tvořící korunu a strom, aby se překreslily: 1 public void nakresli() 2 { koruna.nakresli(); 3 4 kmen. nakresli(); 5 } V těle metody si všimněte, jak se v programu zapisuje příkaz k zaslání zprávy nějaké instanci: napíše se název odkazu na tuto instanci (my máme odkaz uložen v atributu – napíšeme tedy název tohoto atributu), za název se napíše tečka, za tečku název metody realizující reakci na danou zprávu následovaný kulatými zá- @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 116 z 433 Kapitola 3: Vytváříme vlastní třídu 117 vorkami se seznamem hodnot předávaných parametrů (voláme-li bezparametrickou metodu, budou závorky prázdné). Celý příkaz ukončíme středníkem. V předchozím programu jsem v příkazu na řádku 4 schválně zarovnal volání metody pod obdobné volání na předchozím řádku, abyste viděli, že názvy a tečka nemusí být na sebe nalepené, ale může mezi nimi být i mezera a tím i jakýkoliv jiný bílý znak – např. konec řádku. To využijete v okamžiku, kdy se budete rozhodovat, kde rozdělit příliš dlouhý příkaz. Definujte k této metodě doplňkovou metodu smaž(), která nakreslenou instanci na plátně smaže. Test vytvořených metod Metody jsme vytvořili, tak bychom je měli ihned otestovat. Ukážeme si, jak pro ně můžeme vytvořit automatizovaný test. Pomůžeme si přitom metodou souhlas, která je metodou třídy P. Tato metoda otevře dialogové okno, v němž nám položí předem zadanou otázku a pak vrátí volajícímu programu informaci o tom, jak jsem odpověděli. Takže vzhůru do toho. Postupujte následovně: 1. Restartujte virtuální stroj, abyste měli jistotu, že vycházíte z počátečního stavu. 2. Požádejte třídu StromTest, aby z přípravku naplnila zásobník odkazů. 3. V místní nabídce třídy StromTest zadejte příkaz Vytvořit testovací metodu. Obrázek 3.21 Zadání názvu vytvářeného testu 4. V následně otevřeném dialogovém okně zadejte název vytvářeného testu – např. NakresliSmaž, jak je tomu na obrázku 3.21, a své zadání potvrďte. Tím spustíte záznam akcí, které provedete. Navíc se na levém panelu červeně rozsvítí příznak záznam a pod ním se aktivují tlačítka Ukončit a Storno (viz obr. 3.22). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 117 z 433 118 Myslíme objektově v jazyku Java 1.5 Obrázek 3.22 Spuštěný záznam testu je indikován symbolem záznam a aktivovanými tlačítky Ukončit a Storno 5. Pošlete postupně jednotlivým instancím zprávu smaž(). 6. Pošlete třídě P (to je ta třída, která v tomto projektu přibyla) zprávu boolean souhlas(dotaz), ve které jako parametr uvedete text "Stromy smazány?" (nezapomeňte na uvozovky). Obrázek 3.23 Dotaz na to, zda se podařilo stromy opravdu smazat Neobjeví-li se okno s dotazem, bude nejspíše překryto některým jiným oknem. V takovém případě postupně minimalizovat ostatní okna, až se hledané dialogové okno objeví. Mělo by být ve středu obrazovky. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 118 z 433 Kapitola 3: Vytváříme vlastní třídu 119 7. Zadaný řetězec se objeví jako výzva v následně otevřeném dialogovém okně (viz obr. 3.23). Stiskněte ANO, čímž potvrdíte, že stromy byly z plátna opravdu smazány (alespoň doufám, že k tomu došlo i u vás). 8. Po odpovědi na předchozí otázku vám BlueJ oznámí, jakou hodnotu třída P po zaslání zprávy vrátila (vrátila true, protože jsme odpověděli ANO) a zeptá se vás, má-li v příštích testech testovat vracenou hodnotu a pokud ano, tak jakou. Obrázek 3.24 Oznámení vrácené hodnoty a dotaz na to, má-li příště vrácenou hodnotu testovat 9. V dialogovém okně zaškrtněte políčko Předpokládat že, čímž BlueJ požádáte, aby v testu ověřoval očekávanou návratovou hodnotu. Zároveň ověřte, že se bude testovat, jestli je návratová hodnota rovna true (jinými slovy, že při příštím testu potvrdíme, že stromy byly smazány). 10. Nyní pošlete postupně každé z instancí zprávu void nakresli(), čímž ji požádáme o to, aby se znovu nakreslila. 11. Znovu pošleme třídě P zprávu boolean souhlas(dotaz), avšak tentokrát s parametrem "Stromy opět nakresleny?". 12. Opět odpovíme ANO (předpokládám, že se stromy správně nakreslili) a požádáme BlueJ, aby i při příštích bězích ověřoval kladnou odpověď. 13. Stiskem tlačítka Ukončit ukončíme zaznamenávání testu. 14. Restartujte virtuální stroj, čímž zavřete okno plátna a s ním i celou kreslící aplikaci. Test je vytvořen a podíváte-li se do místní nabídky třídy StromTest, zjistíte, že v ní přibyla položka Testovat NakresliSmaž. Zadáte-li tento příkaz, spustí se právě vytvořený test znovu. Spustíme jej a podíváme se, jak by vše probíhalo, kdyby nebylo všechno v pořádku. Zkuste např. odpovědět na první otázku NE. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 119 z 433 120 Myslíme objektově v jazyku Java 1.5 BlueJ zjistí, že na otázku bylo odpovězeno jinak, než jste zadali při zadávání testu, a ukončí test. Otevře pak okno s výsledky testů, kde vám tuto nepříjemnou zprávu oznámí. V horní části okna pak najdete seznam všech provedených testů (prozatím máme definován pouze jeden), přičemž u úspěšně ukončených testů naleznete zelené zaškrtnutí, u neúspěšných testů šedé X (to je náš případ). Spodní část je určena pro podrobnější informace o výsledku vybraného testu. Dole zatím není nic, protože žádný test není vybrán. Klepněte na náš test a dole se zobrazí zpráva, oznamující, že bylo očekáváno true a obdrženo false a poté informace o tom, kde k dané chybě došlo (viz obr. 3.25). Zde se můžeme dozvědět, že k chybě došlo ve třídě StromTest v její metodě testNakresliSmaž a že kritické místo, kde došlo k chybě, nalezneme ve zdrojovém souboru StromTest.java na řádku 72. To by nám mělo stačit k tomu, abychom zjistili, kdo je za chybu zodpovědný (teď nic zjišťovat nebudeme, protože to víme). Obrázek 3.25 Okno s výsledky testů Připravte podobný test i pro vaši vlastní třídu a ověřte s jeho pomocí, že je vše naprogramováno správně. Nejprve testy, pak program? Jak jsem již řekl, testy bychom měli správně dělat před tím, než příslušnou metodu definujeme. Nyní asi namítnete, že před tím, než metodu definujeme, ji nebudeme moci vyvolat a připravit pro ni test. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 120 z 433 Kapitola 3: Vytváříme vlastní třídu 121 Pomoc je jednoduchá: vložte do těla třídy definované metody s prázdnými těly a při tvorbě testu se tvařte, jako že tyto metody fungují. Tak, jak jim postupně budete definovat těla, budou příslušné testy postupně začínat správně chodit. Můžeme si to hned vyzkoušet. Instance všech tří základních geometrických tříd se umějí na požádání posunout. Rozšiřme proto o toto umění i naši třídu Strom. Nejprve do ni vložíme prázdné definice jednotlivých posunových metod (na pořadí nezáleží). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public void posunVpravo() { } public void posunVlevo() { } public void posunVzhůru() { } public void posunDolů() { } public void posunVpravo( int n ) { } public void posunDolů( int n ) { } Upravenou třídu přeložíme a připravíme test, který nazveme např. Posuny. V tomto testu nejprve pošleme třídě P zprávu void zpráva(text) s parametrem "Následuje posun vpravo". Po obdržení této zprávy otevře dialogové okno, v němž vypíše text svého parametru a počká, až stisknete tlačítko OK. Dělám to proto, aby si testující uživatel mohl zapamatovat současnou pozici stromů a ověřit, že se opravdu posunuly vpravo. Pak postupně požádáme všechny instance, aby se posunuly vpravo (ony samozřejmě nezareagují, protože těla metod jsou prázdná, ale to nevadí). Poté pošleme třídě P známou zprávu boolean souhlas(dotaz), které předáme jako parametr text "Posun vpravo vpořádku?\n\nNásleduje posun vlevo". @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 121 z 433 122 Myslíme objektově v jazyku Java 1.5 V textu tohoto parametru bych vás chtěl upozornit na dvě dvojice znaků \n uprostřed textu. Tato dvojice znaků zařídí, že v místě jejího výskytu program odřádkuje. Protože jsou v textu dvojice dvě, odřádkuje dvakrát, takže mezi dotazem a oznámením další akce bude vynechaný řádek – viz obr. 3.26). Obrázek 3.26 Vložení dvojice znaků \n do řetězce způsobí přechod na nový řádek Pak požádáte všechny instance, aby se posunuly vlevo a opět celou sérii zakončíte zprávou třídě P, která vyvolá potvrzovací dialogové okno. Totéž zopakujete pro posun vzhůru a nakonec i pro posun dolů. Obdobný test bychom měli vytvořit i pro přesuny se zadanou vzdáleností. Ty vám ale prozatím odpustím. Později vám ukážu, jak můžete takovýto test doplnit přímým zásahem do kódu testovací třídy. Tím je tedy vše připraveno a můžeme začít programovat. V dalším textu vás již většinou při definici testů nebudu vést za ruku. Budu předpokládat, že si potřebné testy dokážete definovat sami a že si je budete opravdu definovat. Budete tak mít jistotu, že jste vše definovali správně a že vaše příští programy mohou právě vytvořený (a otestovaný) program bez obav používat. K přípravě testů se vrátím jen tehdy, budu-li se domnívat, že se na nich můžeme něco nového naučit. Naprogramujte posunové metody a vyzkoušejte, že všechny pracují podle očekávání. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 122 z 433 Kapitola 3: Vytváříme vlastní třídu 123 Někdy jsou věci složitější Odhaduji, že jste při programování posunových metod použili stejnou fintu, jakou jsme použili při definici metody kresli(), tj. požádat o požadovanou akci postupně korunu a kmen: 1 public void posunDolů() 2 { 3 koruna.posunDolů(); 4 kmen .posunDolů(); 5 } Jestli jste však takto definovanou metodu posunDolů() testovali, dočkali jste se asi překvapení, protože plátno pak vypadalo jako na obr. 3.27 – posouvané stromy mají v korunách díry. Obrázek 3.27 Důvod již známe, protože jsme si o něm povídali v minulé kapitole: při přesouvání se obrazce nejprve smažou v původní pozici a pak se nakreslí v pozici nové. Jenomže při posouvání kmene již byla namístě původního kmene koruna v nové pozici, takže jsme při mazání kmene odmazali i část nově umístěné koruny (v důsledku tohoto postupu také strom vlevo dole odmazal při přesunu kmen stromu vlevo nahoře, ale to není chyba). Problém můžeme řešit dvěma způsoby: napřed obě části stromu smazat a pak je požádat, aby se vykreslili v nové pozici, @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 123 z 433 124 Myslíme objektově v jazyku Java 1.5 po přesunu obou částí do nové pozice korunu ještě jednou překreslit. Kdyby byl náš program příliš pomalý, asi bychom museli zvolit první možnost, při které se nic nekreslí dvakrát. Protože nás však čas výpočtu netlačí, zvolíme druhé řešení, které nám dá méně práce. Výsledná podoba metody je následující: 1 public void posunDolů() 2 { 3 koruna.posunDolů(); kmen .posunDolů(); 4 5 koruna.nakresli(); } Šťouralové zde možná namítnou, že jsme vše opravili pouze pro jeden strom, ale budeme-li přesouvat několik stromů po sobě, budou později přesouvané stromy občas umazávat části stromů přesunutých dříve. Bohužel, mají pravdu. V této části ještě nemáme dostatek nástrojů pro to, abychom mohli tento problém řešit (leda bych naprogramoval plátno jinak, ale já je mám takto jednoduché schválně). Problém vyřešíme až ve druhé části, kdy se seznámíme s novými nástroji a kdy také začneme používat jiné plátno. Použití metod vracejících hodnotu Prozatím jsme dosazovali za parametry pouze čísla, řetězce, hodnoty jiných parametrů anebo vzorečky, kde jsme opět použili čísla nebo hodnoty parametrů. Java nám ale nabízí ještě další možnost – použít návratovou hodnotu metody. Tu získáme tak, že danou metodu zavoláme na místě, kde chceme tuto hodnotu použít. Vyzkoušejme si to v definici metody setPozice(int,int), která umístí strom na pozici zadanou v parametrech. Umísťování koruny je jednoduché, protože koruna má stejné souřadnice jako celý strom. Problém ale nastane při umísťování kmene, jehož souřadnice budeme muset nejprve vypočítat z velikosti koruny. Svislou pozici kmenu získáme relativně snadno – stačí k požadované svislé souřadnici přičíst výšku koruny. Vodorovnou souřadnici budeme muset trochu počítat. Nejsnadněji se k ní asi dostaneme, když rozdíl šířek koruny a kmene vydělíme dvěma (polovina tohoto rozdílu bude vlevo od kmene a polovina vpravo od něj): 1 public void setPozice( int x, int y ) @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 124 z 433 Kapitola 3: Vytváříme vlastní třídu 2 { 3 4 5 6 7 } 125 koruna.setPozice( x, y ); kmen .setPozice( x + (koruna.getŠířka() - kmen.getŠířka) / 2, y + koruna.getVýška() ); koruna.nakresli(); Při vykonávání této metody bude virtuální stroj postupovat následovně: 1. Zavolá metodu setPozice(int,int) instance, na kterou odkazuje atribut koruna a jako hodnoty parametrů ji předá hodnoty svých parametrů. 2. Začne se chystat zavolat tutéž metodu instance, na níž odkazuje atribut kmem(). K tomu ale potřebuje znát hodnoty parametrů. 3. Zavolá metodu getŠířka() instance, na kterou odkazuje atribut koruna, pak zavolá metody getVýška() instance, na níž odkazuje kmen, obě hodnoty odečte a vydělí dvěma. Výsledek přičte k hodnotě parametru x a takto spočtenou hodnotu připraví jako první parametr. 4. Zavolá metodu getVýška() instance, na kterou odkazuje atribut koruna, získanou hodnotu přičte k hodnotě druhého parametru a výsledek připraví jako hodnotu druhého parametru. 5. Konečně zavolá metodu setPozice(int,int) instance, na kterou odkazuje atribut kmen() a předá jí spočtené hodnoty parametrů. 6. Zavolá metodu nakresli() instance, na níž odkazuje atribut koruna. Tím zakryje část koruny, která mohla být odmazána při přesouvání kmene (pokud se nic neodmazalo, tak se koruna jen nakreslí dvakrát). V příštích rozborech se již nebudu tak rozepisovat a místo „zavolá metodu xyz() instance, na kterou odkazuje atribut abc“ budu psát rovnou „zavolá abc.xyz()“. Definice metod vracejících hodnotu Metody vracející hodnotu už umíme použít, takže je nejvyšší čas, abychom se je také naučili definovat. Tyto metody se budou lišit od doposud definovaných metod ve dvou věcech: místo typu void mají v hlavičce uveden typ hodnoty, kterou vracejí, @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 125 z 433 126 Myslíme objektově v jazyku Java 1.5 před ukončení činnosti musí vrátit požadovanou hodnotu – k tomu slouží příkaz return, za který napíšeme výraz, jehož hodnotu bude metoda vracet. V jazyku Java mohou metody vracet pouze jedinou hodnotu. Stejně je tomu i v převážné většině ostatních moderních, objektově orientovaných jazyků. Budete-li potřebovat vrátit více hodnot zároveň, můžete použít obratu popsaného v kapitole Přepravka (Messenger) na straně 230. Metodu getX(), která vrátí aktuální pozici stromu vykresleného danou instancí, bychom pak mohli definovat následovně: 1 public int getX() 2 { return koruna.getX(); 3 4 } Stejně jednoduché jsou i metody getY() a getŠířka(). Metoda getVýška() je jen o maličko složitější, protože výšku stromu nestačí pouze převzít, ale musí si ji vypočítat. Za příkaz return proto uvede příslušný výraz: 1 public int getVýška() 2 { return koruna.getVýška() + kmen.getVýška(); 3 4 } Parametry a návratové hodnoty objektových typů Prozatím jsme pracovali pouze s celočíselnými parametry a návratovými hodnotami. Pro jistotu zde připomínám, že parametry i návratové hodnoty mohou mít libovolný typ. Vyzkoušejme si třeba možnost nastavování a vracení barvy. U stromu není zvykem, aby měla koruna i kmen stejnou barvu. Nic nám ale nebrání definovat metody, které nastaví zvlášť barvu pro korunu a zvlášť barvu pro kmen. Stejně tak můžeme definovat metody, které na požádání tyto barvy vrátí. Pro korunu by jejich definice mohla vypadat následovně: 1 Public Barva getBarvaKoruny() 2 { 3 return koruna.getBarva(); 4 } 5 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 126 z 433 Kapitola 3: Vytváříme vlastní třídu 127 6 public void setBarvaKoruny( Barva nová ) 7 { koruna.setBarva( nová ); 8 9 } V projektu 03_Třídy_Z najdete třídu Strom_3a, která obsahuje definice uvedené v této kapitole. Neobsahuje však řešení „domácích úkolů“. Kromě toho zde najdete i třídu Strom_3aTest, pomocí níž můžete třídu Strom_3a otestovat. 3.12 Přetěžování V minulé kapitole jsem vám říkal, že definujeme-li několik verzí konstruktorů nebo stejnojmenných metod, které se liší pouze počtem a/nebo typem svých parametrů, označujeme to za přetěžování. Občas se mne žáci ptají, jak program pozná, kterou verzi přetíženého konstruktoru či metody má v reakci na danou zprávu použít. Prozradím vám, že klíčovou úlohu zde hrají typy parametrů. Mohli byste si to např. představit tak, že interně používá překladač jméno konstruktoru, které je složené z vlastního jména následované seznamem typů parametrů. Vezměme si třeba obdélník – ten má čtyři konstruktory, které bychom podle předchozího pravidla mohli pojmenovat např. (oddělovací znak # jsem si vymyslel sám): Obdélník Obdélník#int#int Obdélník#int#int#int#int Obdélník#int#int#int#int#Barva Obdobně je to i s metodami. Podíváme-li se např. na metody realizující odpověď na zprávu požadující posun vpravo, budou dvě, pojmenované podle našeho předchozího pravidla: posunVpravo posunVpravo#int Všimněte si, že v tomto interním jméně není ani zmínka o typu návratové hodnoty. Proto nemůžeme definovat metody, které by se lišily pouze v typu návratové hodnoty, protože by pak měly pro překladač stejná jména a to překladač nepřipustí (můžete si to vyzkoušet). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 127 z 433 128 Myslíme objektově v jazyku Java 1.5 Při zápisu zpráv jsem doposud uváděl názvy parametrů tak, jak je BlueJ uváděl v místní nabídce třídy nebo instance. Názvy konstruktorů a metod však již nebudu uvádět se seznamem názvů parametrů, ale se seznamem jejich typů, protože právě podle nich překladač jednotlivé verze přetížené zprávy rozlišuje. 3.13 Zapouzdření V průběhu dosavadního výkladu jsem se několikrát zmiňoval o tom, že by okolní třídy neměly o tom či onom vědět. Definovali jsme soukromé atributy a hovořili jsme i o možnosti definovat soukromé metody a konstruktory. Proč to všechno schovávání? Jednou ze základních a velice ceněných vlastností objektově orientovaných programů je schopnost tzv. zapouzdření. Lidově bychom mohli zapouzdření charakterizovat heslem: „Nikdo nesmí mít šanci zjistit, jak to dělám, že umím to, co umím.“ Takto osamoceně vyslovena vypadá možná tato zásada neurvale, ale věřte, že je to nejvíce ceněná vlastnost celého OOP. Čím jsou programy složitější, tím je důležitější, abychom ani omylem nemohli ovlivnit chod některé jiné části. V naši analogii bychom mohli říci, že pro vozidlo-instanci je mnohem výhodnější, má-li své šuplíky uvnitř, kde o nich ví pouze jeho osádka, než aby je mělo rozmístěné zvenku vozidla, kde může kdokoliv jejich obsah vyměnit, aniž by se o tom posádka dozvěděla. Rozhraní × implementace V této souvislosti se seznámíme se dvěma novými termíny: Rozhraní třídy budeme chápat jako množinu informací, které o sobě třída zveřejní. Mezi rozhraní patří např. vše, co třída označí modifikátorem public. Implementace je způsob, jak je třída naprogramována, tj. jak to dělá, že umí to, co umí. Do rozhraní bychom měli zařadit pouze to, co ostatní části programu o dané třídě opravdu musí vědět. Když jsme například chtěli, aby ostatní programy mohly naši třídu požádat o vytvoření instance, museli jsme zveřejnit její konstruktor. Bu@Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 128 z 433 Kapitola 3: Vytváříme vlastní třídu 129 deme-li chtít, aby ostatní programy mohly zjistit, kde je právě daný strom nakreslen, musíme zveřejnit metody, pomocí nichž mohou tuto informaci získat. Vše, co sice k implementaci požadovaných funkcí potřebuji, ale o čem se domnívám, že ostatní vědět nemusí, označím jako private. Nechci-li, aby ostatní části programu mohly pohybovat jednotlivými částmi stromu bez mého vědomí, nesmím je k nim pustit – označím proto příslušné atributy jako private. Mezi public a private existují ještě mezistupně, ale o těch si povíme až si s objektovým programováním trochu více potykáte. Do rozhraní se počítají i informace, které z hlaviček nevyčtete, ale které by měly být uvedeny v dokumentaci. Sem patří informace o dalších podmínkách, které je třeba dodržet (např. že zadávané souřadnice vytvářeného tvaru musí být větší než 0, že instance metody Plátno je jedináček apod.), o možných vedlejších efektech funkcí (např. co se stane, když obrazec „vycestuje“ z plátna) a řada dalších důležitých sdělení. Tento souhrn informací bývá označován jako kontrakt a musí být uveden v dokumentaci třídy. Jak jsem již řekl, prakticky každý program jeho tvůrci v průběhu doby upravují. Jakmile však třída zveřejní některé své vlastnosti a schopnosti a ostatní třídy začnou její služby používat, nemohou s tím, co o sobě třída zveřejnila, její tvůrci v pozdější době již nic dělat (lépe řečeno neměli by), protože by tím ohrozili chod všech programů, které tyto vlastnosti využívají. Na druhou stranu platí: dokud nezměním rozhraní, mohu si s programem dělat, co chci. Jakékoliv vylepšení či zdokonalení, které se nepromítne do deklarovaného rozhraní, můžeme realizovat s čistým svědomím, že tím funkčnost spolupracujících programů neovlivníme. Přístupové metody Jak jsme si řekli na začátku části o atributech, bývá dobrým zvykem deklarovat všechny atributy jako soukromé. Budeme-li chtít uživatelům naší třídy umožnit zjišťovat či měnit hodnoty některého z atributů, definujeme k tomu zvláštní přístupové metody, které navíc dělíme na čtecí a nastavovací. Toto řešení má několik výhod: Můžeme zařídit, že hodnoty některých atributů bude možno pouze číst, avšak nikoliv nastavovat. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 129 z 433 130 Myslíme objektově v jazyku Java 1.5 Dovolíme-li nastavování nových hodnot atributů, tj. definujeme-li příslušné metody setXxx, můžeme v nich ohlídat, aby uživatel nenastavoval atributům nějaké nesmyslné hodnoty. Při nastavování hodnot atributů můžeme provést ještě potřebné doprovodné akce (např. při změně pozice by se měla instance ve staré pozici smazat a nakresli se v pozici nové). Můžeme dát příslušné metody k dispozici nezávisle na tom, jestli třída dotyčné atributy opravdu má nebo jestli jsou tyto atributy jenom fiktivní. Zvenku třídy, tj. při volání jejích přístupových metod, nemáme šanci zjistit, jestli třída uvnitř pracuje se skutečnými a nebo s vypočítanými atributy. Fiktivní atributy by pro vás měly být něčím důvěrně známým, protože s nimi u našich stromů již dávno pracujeme. Atributy x, y, šířka, výška jsou typickými příklady takovýchto fiktivních atributů. Instance sice tyto atributy nemá, ale tváří se, jako že je má. Když se pak někdo stromu zeptá na jeho souřadnice nebo rozměr, strom se do žádného atributu nedívá (kam by se také díval, když jej nemá) ale zjistí si potřebnou informaci jinak a zjištěnou informaci vrátí. Obdobně je to i s nastavováním těchto atributů. Nastavíte-li třeba novou pozici, strom si nic nikam neukládá, ale pouze zařídí, aby se do požadované pozice přesunul. Když si později vzpomeneme, že fiktivní atributy nahradíme skutečnými (např. proto, že jejich výpočet je příliš pomalý) nebo naopak nahradíme skutečné atributy fiktivními (např. proto, že jejich zjištění či nastavení je tak jednoduché, že nemá smysl si je pamatovat), uživatel se nic nedozví, protože se na atributy bude stále obracet prostřednictvím jejich přístupových metod. Nabídne-li třída v naší analogii nějaké přístupové metody, tj. vybaví-li vozidlo robotem, který vám umí odpovědět na otázku po hodnotě atributu a/nebo robotem, který umí atribut nastavit, je to jako byste měli možnost do našeho robotího vozidla zavolat a na hodnoty v příslušném šuplíku se pověřeného robota-metody zeptat nebo požádat o její změnu. Vy přitom nemáte šanci zjistit, jestli se robot opravdu podíval na hodnotu do šuplíku, anebo jestli ji na poslední chvíli získal nějakou alternativní metodou (třeba si hodil kostkou). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 130 z 433 Kapitola 3: Vytváříme vlastní třídu 131 Hlídání hodnot začneme používat, až budeme umět naprogramovat nějaké rozhodování. S fiktivními atributy jsme ale již pracovali. Vždyť naše stromy nemají atributy, v nichž by měly uloženu svoji pozici nebo rozměr. Nepotřebují je, protože si tyto hodnoty umějí kdykoliv snadno zjistit a stejně snadno je umějí na požádání „nastavit“. Ve třídě Strom máme prozatím definované přístupové metody pouze k fiktivním atributům x, y, šířka, výška a dejme tomu ještě barvaKoruny a barvaKmene. Navíc jsme definovali nastavovací metody pouze k prvním dvěma atributům a u všech ostatních atributů jsme prozatím definovali pouze metody čtecí. Ke skutečným atributům však v této třídě nikoho nepouštíme. Třídy Elipsa, Obdélník a Trojúhelník nabízejí zase čtecí metodu ke svému atributu název, avšak nenabízejí již k němu zapisovací metodu, takže uživatel nemá šanci tento název změnit. Název získaný při svém „narození“ si tak instance podrží až do své „smrti“. Možná, že vás zarazilo, proč jsem pro přístupové metody volil takové divné názvy. Odpověď je jednoduchá: chtěl jsem dodržet konvence pro názvy těchto metod. Konvence pro názvy přístupových metod Pro názvy přístupových metod platí zvláštní konvence, které je vhodné dodržovat, protože jsou na ně zvyklí nejenom programátoři, ale je na nich postavena i funkčnost některých programů a technologií: Názvy metod sestávají z předpony následované názvem příslušného atributu, přičemž je jedno, jestli je daný atribut skutečný nebo fiktivní. Za předponou se první písmeno tohoto názvu píše velké, i když je ve skutečnosti (alespoň dle konvencí) malé. Nastavovací metody (tj. metody, které nastavují hodnotu příslušného atributu) používají předponu set. Čtecí metody (tj. metody, které vracejí hodnotu příslušného atributu) používají příponu get. V případě, že vracejí logickou hodnotu (tj. hodnotu typu ANO/NE, přesněji true/false), mohou mít předponu is. Podle těchto předpon označují programátoři často nastavovací metody jako „setry“ a čtecí metody jako „getry“. Já se však v této učebnici budu držet českých termínů. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 131 z 433 132 Myslíme objektově v jazyku Java 1.5 Kontrakt Rozhraní není definováno pouze hlavičkami veřejných metod. Ty mohou definovat jen syntaktická pravidla, která bude překladač kontrolovat při překladu. Vedle těchto syntaktických pravidel ale do rozhraní patří i tzv. kontrakt (mohli bychom jej nazvat dohodou mezi tvůrcem třídy či metody a jejím uživatelem). Do kontraktu zařazujeme další podmínky, které je nutno při práci s danou třídou resp. při volání další metody splnit, avšak které nemůžeme specifikovat prostředky jazyka, jako to děláme např. s požadovaným typem parametrů. Můžeme např. vyžadovat, aby číselný parametr byl nezáporný nebo dokonce kladný, aby řetězcový parametr obsahoval minimální či maximální zadaný počet znaků nebo aby splňoval nějakou jinou podmínku. Tyto podmínky nemusí být kladeny pouze na konkrétní parametry, ale např. i na vzájemné vztahy mezi jednotlivými parametry, resp. mezi parametry a okolním světem. Všechny záležitosti týkající se kontraktu (tj. co se od třídy, resp. metody očekává), by měly být uvedeny v dokumentaci třídy, resp. metody. Jinak se totiž o nich uživatel nemá šanci dozvědět a mohl by je používat špatně. Např. v našem příkladu s geometrickými tvary patří do kontraktu informace o tom, že konstruktor vyžaduje pouze kladné hodnoty souřadnic a rozměrů, co že jsou to vlastně souřadnice obrazce (vzdálenost levého horního rohu opsaného obdélníku od levého horního rohu plátna), že plátno je jedináček, co přesně znamenají při konstrukci trojúhelníku zadané směry, atd., atd. Dodržení kontraktu sice překladač kontrolovat nedokáže, nicméně počítejte s tím, že jeho nedodržení vede často k havárii programu nebo k jeho podivnému, nepředvídatelnému chování. 3.14 Kvalifikace a klíčové slovo this Název proměnné nebo třídy následovaný tečkou je označován jako kvalifikace. Kvalifikace vlastně určuje, komu že chceme danou zprávu poslat. Kvalifikace metod Každou metodu je třeba před jejím zavoláním kvalifikovat, protože musíme překladači sdělit, čí metodu voláme, tj. komu že posíláme příslušnou zprávu. Pro kvalifikaci platí následující pravidla: Posíláme-li zprávu třídě, musíme volání metody kvalifikovat názvem třídy. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 132 z 433 Kapitola 3: Vytváříme vlastní třídu 133 Posíláme-li zprávu instanci, musíme volání metody kvalifikovat odkazem na tuto instanci. Předchozí pravidlo má jedinou výjimku: voláme-li v metodě instance jinou metodu té samé instance, toto volání kvalifikovat nemusíme. Překladač totiž ví, že volat nekvalifikovanou metodu instance je nesmysl, takže pokud tak učiním, tj. pokud v programu zavolám nekvalifikovanou metodu instance, pokusí se ji kvalifikovat tou instancí, jejíž metodu právě překládá. Pokud bychom ale chtěli v metodě instance kvalifikovat i metody této instance, můžeme pro jejich kvalifikaci použít klíčové slovo this. Toto klíčové slovo zastupuje odkaz na instanci, v jejíž metodě se nacházíme. Metodu třídy můžeme teoreticky kvalifikovat i odkazem na kteroukoliv z jejích instancí, ale takováto kvalifikace není považována za programátorsky čistou, protože může při analýze programu vyvolat dojem, že se jedná o metodu instance. Proto tento způsob kvalifikace nebudu používat. Situaci, kdy si překladač kvalifikaci domyslí, označujeme implicitní kvalifikace, na rozdíl od explicitní kvalifikace, při níž programátor kvalifikuje metodu sám. Nyní jsme teoreticky vyzbrojeni, takže můžeme hned zkusit nabyté vědomosti použít. Vše si ukážeme na následujícím příkladu: Příklad: Definujte metodu void zarámuj(), která svoji instanci na plátně zarámuje, tj. upraví rozměry plátna tak, aby na něm byla pouze její instance. Rozmysleme si nejprve, jak bychom mohli postupovat: Velikosti plátna změníme zavoláním jeho metody setRozměr(int,int). Abychom mohli zavolat metodu plátna, musíme mít nejprve odkaz na toto plátno. Získáme jej zavoláním metody getPlátno() třídy Plátno. Odkaz na instanci, který takto získáme, nemusíme odkládat do žádného atributu, ale můžeme jej přímo použít k tomu, abychom zavolali metodu nastavující rozměr (otazníky označují zatím neznámé části programu): Plátno.getPlátno().setRozměr( ???, ??? ); @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 133 z 433 134 Myslíme objektově v jazyku Java 1.5 Velikost plátna má být stejná jako velikost příslušné instance. Tu zjistíme tak, že zavoláme její metody getŠířka() a getVýška(). Obdržené výsledky opět nemusíme nikam ukládat, ale můžeme je hned předat jako parametry metodě pro nastavení velikosti plátna. Příkaz tedy bude vypadat: Plátno.getPlátno().setRozměr( getŠířka(), getVýška() ); Po změně rozměru se plátno smaže. To nám ale nevadí, protože stejně musíme přesunout naši instanci do levého horního rohu plátna. Po předchozích úvahách je již asi jasné, jak by mohla definice metody zarámuj() vypadat: 1 public void zarámuj() 2 { 3 Plátno.getPlátno().setRozměr( getŠířka(), getVýška() ); setPozice( 0, 0 ); 4 5 } Pokud bychom chtěli kvalifikovat i metody vlastní instance, získal by kód tvar: 1 public void zarámuj() 2 { 3 Plátno.getPlátno().setRozměr( this.getŠířka(), this.getVýška() ); 4 this.setPozice( 0, 0 ); 5 } Zkuste sami navrhnout test této metody. Můžete jej definovat např. tak, že po načtení přípravku do zásobníku požádáte o zarámování první instanci a pomocí metody P.souhlas se zeptáte, jestli je výsledek správný. Pak můžete zobrazenou instanci smazat a požádat o zobrazení další instance a opět se zeptat na správnost výsledku. Tentokrát by se mělo okno přizpůsobit velikosti nově rámované instance. Kvalifikace atributů Totéž, co jsme si před chvílí říkali o metodách, platí i o atributech. Ty však bývají většinou soukromé, takže o ně „cizí“ žádat nemohou a „domácí“ je kvalifikovat nemusejí. Proto se s jejich kvalifikací příliš často nepotkáte. Někteří programátoři však kvalifikují vše, takže pak mají „domácí“ atributy kvalifikované klíčovým slovem this. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 134 z 433 Kapitola 3: Vytváříme vlastní třídu 135 V jednom případě je ale kvalifikace klíčovým slovem this nutná: shoduje-li se název parametru s názvem atributu a potřebuji-li v dané metodě pracovat s oběma (většinou potřebuji parametru přiřadit hodnotu atributu). Tato situace bývá relativně častá, protože programátoři bývají líní vymýšlet nové názvy a navíc bývá takovýto název parametru nejnázornější. Program pracuje podle zásady „bližší košile než kabát“. Když se proto v metodě objeví parametr, který se jmenuje stejně jako atribut, metoda přestane na stejně pojmenovaný atribut vidět. Jedinou možností jak jej „zviditelnit“ je „přejmenovat jej“ pomocí přidaného this. Ukažme si to na příkladu třídy Pozice, jejíž instance budou mít za úkol si zapamatovat vodorovnou a svislou souřadnici zadané pozice. Tuto třídu bychom mohli definovat např. následovně: 1 public class Pozice 2 { 3 private int x; private int y; 4 5 6 7 public Pozice( int x, int y ) { 8 9 this.x = x; 10 this.y = y; } 11 12 public int getX() 13 14 { //Tady se x s ničím nehádá, takže this používat nemusíme 15 16 return x; //ale můžeme, tj. lze napsat: return this.x; 17 } 18 19 public int getY() { 20 21 return y; 22 } 23 24 public void setPozice( int x, int y ) 25 { 26 this.x = x; 27 this.y = y; 28 } 29 30 public void setPozice( Pozice pozice ) 31 { 32 this.x = pozice.x; 33 this.y = pozice.y; } 34 } @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 135 z 433 136 Myslíme objektově v jazyku Java 1.5 Někteří žáci se podivují poslední metodě. Ptají se: ʺProč mám nastavovat pozici, když ji už mám?ʺ Poslední metodu použijete tehdy, máte-li jednu instanci třídy Pozice a potřebujete-li, aby druhá instance ukazovala na tu samou pozici. Předáte proto metodě první instanci jako parametr a metoda zkopíruje hodnoty jejích atributů do atributů druhé instance. Už jsem po vás dlouho nic nechtěl ☺. Zkuste definovat třídu Rozměr, jejíž instance by si pro změnu pamatovali zadanou šířku a výšku. 3.15 Atributy a metody třídy (statické atributy a metody) Když jsme si v minulé kapitole hráli s geometrickými obrazci, ukazovali jsme si, že každý z nich poskytuje metody, které mohou pohnout se zadaným obrazcem o předem daný počet bodů. Tento počet byl uložen v atributu třídy nazvaném krok. Jeho hodnotu bylo možno nastavit metodou setKrok(). Tato možnost ale představuje pro náš strom značné nebezpečí. Zkuste požádat např. třídu Elipsa, aby zmenšila velikost svého kroku na 25, a pak znovu spusťte test Posuny. Jeho průběh vás určitě nepotěší. Atributy třídy Jednou z možností, jak chování naší třídy vylepšit, je zavést pro ni její vlastní atribut krok a ten pak používat při posunu stromů pomocí bezparametrických metod. Atributy třídy definujeme stejně jako atributy instancí, pouze mezi jejich modifikátory přidáme klíčové slovo static. Na pořadí modifikátorů přitom nezáleží, ale doporučuji vám, abyste si na nějaké zvykli. Já ve svých programech např. uvádím modifikátor přístupu vždy jako první a modifikátor static až za ním. Podle modifikátoru static bývají atributy a metody třídy označovány často jako statické na rozdíl od atributů a metod instancí, které bývají občas označovány jako nestatické. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 136 z 433 Kapitola 3: Vytváříme vlastní třídu 137 Po zavedení statického atributu krok můžeme všechny bezparametrické posunové metody upravit tak, aby posunuly strom o tolik bodů, kolik je hodnota tohoto atributu. Část kódu, která s touto úpravou souvisí, by mohla vypadat např. následovně: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 private static int krok = 50; public void posunVpravo() { posunVpravo( krok ); } public void posunVlevo() { posunVpravo( -krok ); } public void posunVzhůru() { posunDolů( krok ); } public void posunDolů() { posunDolů( -krok ); } Na rozdíl od atributů instancí, které můžeme inicializovat buď přímo v deklaraci nebo později v konstruktoru, pro inicializaci atributů třídy prozatím žádný konstruktor neznáme (existuje, ale do začátečnických kapitol přeci jenom nepatří). Zbývá nám tedy prozatím pouze inicializace v deklaraci. Naštěstí to však není žádné velké omezení, protože inicializovat můžeme i voláním nějaké metody, která potřebnou hodnotu nejprve spočítá. Metody třídy Stejně jako atributy třídy existují i metody třídy a i ony se definují vložením klíčového slova static mezi modifikátory. Jejich výhodou je, že je můžeme zavolat ještě před tím, než vznikne její první instance. Slouží proto často k přípravě prostředí, ve kterém následně instance vznikne, případně k definici metod, které nejsou na žádnou instanci vázány (takto je např. definována většina matematických funkcí). Jako metody třídy je nutné definovat např. metody, které zjišťují nebo nastavují hodnoty atributů třídy (i ty můžeme např. číst a nastavovat ještě před tím, než vznikne první instance dané třídy). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 137 z 433 138 Myslíme objektově v jazyku Java 1.5 Před chvilkou jsme si vysvětlovali, že atributy třídy můžeme inicializovat i prostřednictvím volání metod. Protože se tyto atributy inicializují ještě před tím, než se třída poprvé použije, musíme k jejich inicializaci použít statických metod, protože jedině ty můžeme volat ještě před tím, než vznikne jakákoliv instance. Předpokládám, že nyní byste dokázali bez problému definovat metody pro zjištění a nastavení hodnoty atributu krok. Abych vám rozšířil obzory, tak k těm dvěma základním přidám ještě bezparametrickou verzi metody setKrok(), která o zadání velikosti kroku požádá uživatele. Využije k tomu statickou metodu zadej(Object,int) třídy P, které předá jako první parametr text výzvy k zadání hodnoty (ten má deklarován typ Object, takže sem můžete zadat cokoliv). Jako druhý parametr ji předá implicitní hodnotu, kterou bude v našem případě aktuální hodnota kroku. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static int getKrok() { return krok; } public static void setKrok( int krok ) { Strom.krok = krok; } public static void setKrok() { krok = P.zadej( "Zadejte novou velikost kroku:", krok ); } Jak jsem řekl, metody třídy nezávisejí na žádné instanci a můžeme je volat ještě před tím, než první instance vznikne. Je proto logické, že v metodách třídy nemůžeme používat atributy a metody instancí, přesněji řečeno atributy a metody instancí kvalifikované implicitně nebo explicitně klíčovým slovem this. Překladač totiž nemá žádnou informaci o tom, která instance by se v daném okamžiku mohla za this skrývat a čí metodu má proto vyvolat (komu má danou zprávu poslat). Vytvoříme-li si však v metodě třídy vlastní instanci, tak její metody samozřejmě používat můžeme. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 138 z 433 Kapitola 3: Vytváříme vlastní třídu 139 1. Naprogramujte podobné rozšíření i pro vaši třídu. 2. Definujte statickou metodu zarámuj(int,int), které byste dodali výšku a šířku požadovaného stromu jako parametry, a ona by daný strom vytvořila a upravila podle něj velikost plátna. 3. Definujte statickou metodu alej(), která vytvoří alej ze dvou řad stromů po třech. Můžete si vybrat, jestli bude alej orientovaná vodorovně, svisle nebo šikmo. 4. Definujte potřebné testy. Protože druhý a třetí úkol již od vás vyžaduje trochu přemýšlení, neboť není jenom jednoduchou variací toho, co jsme už naprogramovali, přidám pro ty, kterým se vlastní řešení nedaří, ukázku toho, jak by mohlo řešení vypadat. Testy si snad dokážete definovat sami. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public static void zarámuj( int šířka, int výška ) { Plátno.getPlátno().setRozměr( šířka, výška ); new Strom( 0, 0, šířka, výška ); } public static void alej() { Plátno.getPlátno().setRozměr( 400, 350 new Strom( 100, 0 ); new Strom( new Strom( 50, 100 ); new Strom( new Strom( 0, 200 ); new Strom( } ); 300, 0 ); 250, 100 ); 200, 200 ); 3.16 Lokální proměnné Často se stává, že si v metodě potřebujeme na chvilku něco zapamatovat. V takovém okamžiku by se nám hodilo něco jako „atribut metody“. Taková věc opravdu existuje a říká se jí lokální proměnná. Lokální proto, že o ní ví pouze její bezprostřední okolí (v našem případě kód uvnitř metody) a proměnná proto, že hodnotu, kterou si do ní uložíme, můžeme za chvilku změnit. Lokální proměnné se deklarují téměř stejně jako atributy. Jejich deklarace a použití se liší pouze v několika drobnostech: Deklarují se uvnitř metod, tj. v jejich těle. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 139 z 433 140 Myslíme objektově v jazyku Java 1.5 V jejich deklaraci nesmíme použít modifikátory přístupu ani modifikátor static. Mimo jejich metodu o nich nikdo neví. Bude-li proto jiná metoda definovat stejně pojmenované lokální proměnné, budou možná stejně pojmenované, ale budou to naprosto jiné proměnné. Je to obdobné, jako když budete mít doma morče pojmenované Ferda (programátorsky: budete mít lokální proměnnou Ferda typu Morče) a váš kamarád na druhém konci města bude mít stejně pojmenované morče. Obě jsou to morčata, obě mají stejné jméno, ale nikdo nepředpokládá, že nakrmíte-li vašeho Ferdu, přestane mít kamarádův Ferda hlad. Stejně pohlížejte i na lokální proměnné metod. Podíváme-li se na naší „oblíbenou analogii“ s roboty, mohli bychom si lokální proměnné představit jako kapsy, do nichž si robot ukládá potřebné informace. Do kapes robotovi nikdo nevidí. Představují tak třetí, nejsoukromější vrstvu uložených informací. Před jejich prvním použitím jim musíte přiřadit nějakou počáteční hodnotu. Neučiníte-li tak, ohlásí překladač chybu, protože odmítá vytvořit program, který by pracoval s nějakým smetím, jež by se zrovna nacházelo v paměti na místě, které by pro danou proměnnou vyhradil. Jakmile metodu opustíte, proměnná se zruší a při příštím spuštění metody se znovu vytvoří. Není proto možné uchovávat v lokálních proměnných cokoliv, co si potřebujeme pamatovat mezi jednotlivými voláními dané metody. K tomu musíte použít atributy. Podíváte-li se na vlastnosti lokálních proměnných, zjistíte, že parametry jsou vlastně zvláštním případem lokálních proměnných. Jsou to lokální proměnné, jejichž deklarace metoda vysunula do své hlavičky, aby o nich okolní program věděl a mohl nastavit jejich počáteční hodnoty. Parametry se od ostatních lokálních proměnných liší opravdu pouze tím, že jejich počáteční hodnoty nenastavuje metoda, ale program, který tuto metodu volá. Pak už k nim ale volající program ztratí přístup a metoda si s nimi může dělat, co uzná za vhodné, aniž by to mohl volající program jakkoliv ovlivnit. Takže nyní už všechno víme a můžeme začít programovat. Vrátíme se k definici konstruktoru se čtyřmi parametry (viz program na straně 103, řádek 13). V tomto @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 140 z 433 Kapitola 3: Vytváříme vlastní třídu 141 konstruktoru jsme několikrát použili výraz výška/3. Kolikrát je použit, tolikrát se musí spočítat. Mohli bychom proto program upravit tak, že bychom si definovali pomocnou lokální proměnnou, kterou bychom nazvali např. v3. Do ní bychom uložili výsledek výrazu výška/3 a pak bychom ji dosadili místo tohoto výrazu. Upravený program by vypadal následovně: 1 public Strom( int x, int y, int šířka, int výška ) 2 { int v3 = výška / 3; 3 koruna = new Elipsa ( x, y, šířka, 2*v3, Barva.ZELENÁ ); 4 kmen = new Obdélník( x+9*šířka/20, y+2*v3, 5 šířka/10, v3, Barva.ČERVENÁ ); 6 7 } Program se po úpravě teoreticky zrychlí, protože se výraz již nebude počítat třikrát, ale pouze jednou. Zrychlení ale není nejčastějším důvodem, proč zavádíme lokální proměnné (navíc je v tomto případě prakticky neměřitelné). Dalším (a často důležitějším) důvodem bývá zpřehlednění programu a v neposlední řadě i snížení počtu chyb zavlečených v důsledku opakovaného opisování složitých výrazů. Je tu ale ještě jeden důvod. Dobrý programátor se snaží nikdy nepsat stejný kód dvakrát. Nejde jen o snížení pravděpodobnosti vzniku chyby, ale také o to, že se tím výrazně zjednoduší případná pozdější modifikace programu. Vezměme si např. situaci, kdy bychom se rozhodli změnit proporce stromu a definovat výšku kmene ne jako třetinu celkové výšky stromu, ale např. jako jeho polovinu nebo čtvrtinu. V původním programu bychom museli vyhledat všechny výskyty výrazu výška/3, nahradit je novým výrazem a věřit, že jsme žádný z nich nepřehlédli. V upraveném programu je to mnohem jednodušší: stačí změnit výraz, kterým přiřazujeme počáteční hodnotu proměnné v3. Upravte program tak, aby se do proměnné v3 ukládala pouze polovina požadované výšky stromu, a prožeňte jej testy. Tak co, vyzkoušeli jste to? Přišli jste na chybu? Předpokládám, že ano: chyba byla v tom, že jsme sice do proměnné v3 vložili budoucí výšku kmene, ale neupravili jsme podle toho výraz, který vypočítává výšku koruny a v dalším příkazu výraz, který vypočítává posunutí svislé souřadnice kmene vůči svislé souřadnici celého stromu. Celý strom je proto vyšší, než jsme požadovali (viz obr. 3.28). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 141 z 433 142 Myslíme objektově v jazyku Java 1.5 Obrázek 3.28 Vlevo obrázek „třetinového“ stromu, vpravo obrázek „polovičního“ stromu. Oba stromy mají nastavenu výšku 150. Lidově řečeno odflákli jsme to (ještě že máme k dispozici ty automatické testy). Podívejme se proto, jak bychom měli program upravit, aby v něm bylo možno snadno změnit poměr výšky kmene k velikosti stromu. A když už o takové možnosti uvažujeme, definujeme rovnou nový konstruktor, v němž umožníme zadat jako parametry podíl výšky a šířky kmene na výšce a šířce stromu tj. kolikrát je celý strom širší, resp. vyšší než samotný kmen. 1 public Strom( int x, int y, int šířka, int výška, 2 int podílVýškyKmene, int podílŠířkyKmene ) 3 { int výškaKmene = výška / podílVýškyKmene; 4 int výškaKoruny = výška - výškaKmene; 5 int šířkaKmene = šířka / podílŠířkyKmene; 6 7 int posunKmene = (šířka – šířkaKmene) / 2; koruna = new Elipsa ( x, y, šířka, výškaKoruny, Barva.ZELENÁ ); 8 9 kmen = new Obdélník( x+posunKmene, y+výškaKoruny, šířkaKmene, výškaKmene, Barva.ČERVENÁ ); 10 11 } Nový konstruktor si hned vyzkoušejte. Při tvorbě testovací metody vám doporučuji nejprve smazat a poté i odstranit ze zásobníku odkazů všechny instance z přípravku a pak při vytváření nových instancí použít jména strom1 až strom4. Za chvilku si ukážeme, čeho tím dosáhnete. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 142 z 433 Kapitola 3: Vytváříme vlastní třídu 143 Vyzkoušejte si použití lokálních proměnných ve vašich vlastních třídách. Zkuste v nich také definovat nějaký univerzálnější konstruktor obdobný šestiparametrovému konstruktoru třídy Strom. Když teď máme definovaný univerzální konstruktor, měli bychom příslušně upravit i testovací třídu. Jednou z možností je definovat nový přípravek, který bude obsahovat i stromy, při jejichž konstrukci jsme použili všech šest parametrů. 3.17 Konstanty a literály V programech často používáme nejrůznější „magické hodnoty“, které se v průběhu programu nemění. Budeme-li chtít např. pracovat s počtem dnů v týdnu, budeme neustále používat číslo 7. Problém nastane, když se při některé z pozdějších úprav rozhodneme, že místo počtu dnů v celém týdnu bude pro náš program výhodnější používat pouze počet pracovních dnů, tj. 5 (alespoň prozatím to tak je). Taková úprava pak znamená prolézt celý program a všechny sedmičky nahradit pětkami. Již samotná tato představa je dostatečně nepříjemná. Noční můrou se ale stane, pokud je program opravdu rozsáhlý a navíc je v něm řada různých „sedmiček“ – některé sedmičky budou např. znamenat, že v červenci začínají prázdniny a další sedmičky budou oznamovat, že pracujeme od 7 hodin ráno. Rozumní programátoři proto takovéto „magické hodnoty“ nepoužívají a dávají přednost pojmenovaným konstantám. Ty se v Javě definují stejně jako atributy, pouze se mezi jejich modifikátory uvede klíčové slovo final. Jako konstanty je možné deklarovat i parametry a lokální proměnné – opět toho dosáhneme tak, že jejich deklaraci uvodíme klíčovým slovem final. Výše popsané „magické hodnoty“ označujeme jako literály. Literál je konstanta zapsaná svoji hodnotou – např. 7 (celočíselný literál), ʺAhojʺ (řetězcový literál) apod. Naproti tomu konstanty, o nichž jsme právě hovořili a které deklarujeme podobně jako proměnné, označujeme jako pojmenované konstanty, protože ve své definici dostanou jméno, kterým se na ně v programu odvoláváme. Budu-li v dalším textu hovořit o konstantách, bud tím vždy myslet pojmenované konstanty. Pokud bych chtěl hovořit o literálech, vždy to výslovně uvedu. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 143 z 433 144 Myslíme objektově v jazyku Java 1.5 Názvy konstant, které jsou atributy třídy (tj. názvy statických konstant), se podle konvencí píší velkými písmeny s tím, že se jednotlivá slova názvu oddělují znaky podtržení. U názvu ostatních konstant se již tato konvence tak přísně nedodržuje. Konstantám můžeme přiřadit hodnotu pouze jednou a již nikdy ji nemůžeme změnit: Statickým konstantám, tj. konstantám, které jsou atributy třídy, přiřadíme jejich hodnotu hned v deklaraci. Nestatickým konstantám, které nemají svoji hodnotu přiřazenou v deklaraci, musíme přiřadit hodnotu v konstruktoru. Neučiníme-li tak, překladač to označí jako chybu. Konstantním parametrům se přiřadí hodnota při volání příslušné metody. Lokálním konstantám je třeba přiřadit hodnotu hned v jejich deklaraci. Poznámka o dobrých mravech: Z číselných literálů se doporučuje používat pouze hodnoty 0 a 1. Ostatní hodnoty je lépe definovat jako pojmenované konstanty. Dokonce i nuly a jedničky se mají používat pouze v situacích, kdy tyto hodnoty neoznačují nic, co by se mohlo v některé z příštích verzí programu změnit – např. v současné době mám jen jedno kolo (auto, peněženku, manželku, …), ale dokážu si představit situaci, kdy se tato jednička změní. Obdobně i řetězcové literály je vhodné používat pouze v situacích, kdy se daný řetězec použije pouze jednou (např. v chybovém hlášení). Chcete-li však program lokalizovat do více jazyků, je vhodné i jednou použité textové řetězce pojmenovat a definovat všechny na jednom místě, kde je lze snadno nahradit jejich překlady. Konstantní parametry se příliš nepoužívají, i když většina metod jejich hodnotu ve svém těle nemění. Používají se pouze v některých situacích, kdy definice jazyka použití konstanty vyžaduje. O těchto případech si podrobněji povíme, až budeme probírat náročnější pasáže. Doplňme nyní definici třídy strom o konstanty IMPLICITNÍ_POMĚR_VÝŠKY a IMPLICITNÍ_POMĚR_ŠÍŘKY které budou obsahovat výchozí hodnoty 2 a 10, s nimiž @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 144 z 433 Kapitola 3: Vytváříme vlastní třídu 145 jsme stromy začali vytvářet. Upravme pak čtyřparametrický konstruktor tak, aby pouze zavolal konstruktor šestiparametrický. 1 2 3 4 5 6 7 8 9 public static final int IMPLICITNÍ_POMĚR_ŠÍŘKY = 2 public static final int IMPLICITNÍ_POMĚR_VÝŠKY = 10; public Strom( int x, int y, int šířka, int výška ) { this( x, y, šířka, výška, IMPLICITNÍ_POMĚR_ŠÍŘKY, IMPLICITNÍ_POMĚR_VÝŠKY ); } Všimněte si, že jsem v ukázce definoval konstanty jako veřejné (public). Konstanty primitivních datových typů totiž nemůže nikdo změnit, a proto se konstanty, které by se někomu mohly hodit, většinou deklarují jako veřejné. Pokud si však nemyslíte, že by bylo užitečné, aby někdo hodnotu takové konstanty znal, ponechte je raději dále jako soukromou. Konstanty objektových typů Konstanty nemusí být pouze hodnotami primitivních datových typů. Připomeňme si však, že hodnotou atributu objektového typu je odkaz na příslušnou instanci. Deklarujeme-li tedy atribut objektového typu jako konstantní, překladač zabezpečí, abychom nezměnili tento odkaz. Co se však děje s vlastní instancí jej nezajímá. Vzpomeňte si na náš strom a jeho atributy koruna a kmen. Na počátku jsme jim v konstruktoru přiřadili počáteční hodnotu (vytvořili jsme příslušnou instanci a do atributu uložili odkaz na ni) a pak už jsme všude pracovali stále s těmi samými objekty. Mohli jsme je přesouvat, mohli jsme měnit jejich velikost či barvu, ale stále to byla ta samá elipsa či obdélník, které jsme vytvořili při konstrukci stromu. Pokud bychom chtěli tuto neměnnost instancí nějak zabezpečit, mohli bychom korunu a kmen deklarovat jako konstanty instance, a překladač by pak dohlédl na to, abychom je v žádné metodě nezaměnili za jiné. Nijak by však nesledoval, co s těmito instancemi provádíme. Zveřejňování konstant objektových datových typů, tj. deklarace těchto konstant jako veřejných atributů, je trochu složitější než u konstant s hodnotami primitivních typů. Teď to zatím nebudu rozebírat a vrátím se k této otázce později v kapitole Hodnotové a referenční typy na straně 222. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 145 z 433 146 Myslíme objektově v jazyku Java 1.5 Správná podoba literálů Nadpis tohoto oddílu možná některé z vás zarazil. Proč si máme povídat o správné podobě literálů, když jsem je před chvílí tak pomluvil. Důvod je jednoduchý: bez literálů se programovat nedá. Použití pojmenovaných konstant je sice mnohem výhodnější, jenže těmto konstantám musíme jednou přiřadit počáteční hodnotu, a to bez literálů nejde. Probereme si nyní postupně literály jednotlivých primitivních typů. Přehled zakončíme řetězcovými literály. boolean S logickými literály to je nejjednodušší, protože mohou nabývat pouze dvou hodnot: true označuje pravdivou hodnotu a false hodnotu nepravdivou. int Ani s celočíselnými literály to nebude složité. Celá čísla jste jistě všichni znali a uměli psát ještě dříve, než jste začali chodit do školy. V programu se píší prakticky stejně, jako jste zvyklí z obyčejného života. Několik odchylek by se ale našlo: V programu nesmíte zapsat uvnitř celého čísla mezeru – to by byla chyba. Maximální celé číslo proto zapíšete 2147483647. Zapisujete-li číslo v desítkové soustavě, nesmí začínat nulou – to by si počítač myslel, že zadáváte hodnotu v osmičkové soustavě. V programech se občas čísla zapisují i v jiných číselných soustavách než v desítkové. K této možnosti se ale vrátím až v třetí části knihy, kdy si ukážeme situace, v nichž je takový zápis užitečný. double S reálnými čísly je to trochu složitější – ta můžete zapsat dvěma různými způsoby. Buď jako obyčejné desetinné číslo, např. 3.1415926, nebo v tzv. semilogaritmickém tvaru: Desetinné číslo Oproti našim zvyklostem se v Javě (stejně jako v ostatních programovacích jazycích) nepoužívá desetinná čárka, ale desetinná tečka. Oproti celým číslům mohou desetinná čísla začínat i nulou. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 146 z 433 Kapitola 3: Vytváříme vlastní třídu 147 Lenoši ocení, že nemá-li číslo celou část, nemusíte před desetinnou tečku psát nic – např. číslo 0,123 můžete zapsat .123. Nicméně zápis 0.123 je také správný. Semilogaritmický tvar (někdo tomuto tvaru říká „vědecký tvar“): Sestává ze tří částí: mantisy, oddělovače exponentu a exponentu. Mantisa je obyčejné desetinné číslo. Jako oddělovač exponentu slouží písmeno e nebo E. Exponent je celé číslo se znaménkem. Při použití semilogaritmického tvaru můžete jedno a to samé číslo zapsat mnoha způsoby. Několik možných podob zápisu čísla 123,45 uvádí následující tabulka. U každé podoby je přitom uveden její ekvivalent, jak by jej zapsal matematik. Java 0.012345e+4 1.2345e2 123.45E0 12345E-2 Matematik 0,012345.104 1,2345.102 123,45.100 12345.10-2 Java .12345e+03 12.235E01 1234.5e-1 123450e-3 Matematik 0,12345.103 12,345.101 1234,5.10-1 123450.10-3 Celé číslo Jsou situace, kdy potřebujeme, aby počítač pracoval s celým číslem jako kdyby bylo reálné. K tomu mám dvě možnosti: Za číslo přidáme příponu D nebo d – např. 2D, 123d. Číslo napíšeme jako desetinné, např. 2.0, 123.0. String Datový typ String, tj. typ znakových řetězců, je jediný objektový datový typ, který má vlastní literál. Pro jeho psaní platí následující pravidla: Literál vytvoříme tak, že zadávaný řetězec zapíšeme mezi uvozovky – např. "Zadávaný řetězec". Celý řetězec musí být na jednom řádku. Potřebujeme-li zadat dlouhý řetězec, který se nám nechce psát na jeden řádek, můžeme jej vytvořit z „jednořádkových“ částí, mezi něž vložíme znaménko + (znaménkem + ale můžeme spojovat i řetězce na témže řádku) např.: "Dlouhý řetězec, " + "který rozdělíme" + @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 147 z 433 148 Myslíme objektově v jazyku Java 1.5 "na několik řádků," + "aby se nám lépe četl." Potřebujeme-li vložit do řetězce přechod na nový řádek, vložíme do něj dvojici znaků \n – např. "První řádek\ndruhý řádek\ntřetí řádek". Potřebujeme-li vložit do řetězce uvozovky, vložíme do něj dvojici \" – např. řetězec "Cituji: \"Bude dobře!\"" se vysadí jako: Cituji: ʺBude dobře!ʺ Potřebujeme-li vložit do řetězce znak obráceného lomítka, vložíme dvojici obrácených lomítek – např. řetězec "Uvozovky: \\\"" se vytiskne jako Uvozovky: \ʺ. null null je společným literálem pro všechny objektové datové typy. Jak jsme si již mnohokrát řekli, když máte atribut nebo proměnnou objektového typu, uchováváte v ní odkaz na instanci daného typu. Občas je ale potřeba dát vědět, že v dané proměnné ještě žádný odkaz není. K tomu právě slouží hodnota null, která zastupuje odkaz nikam. V projektu 03_Třídy_Z najdete třídy Strom_3b, která obsahuje definice atributů a metod třídy Strom uvedené v kapitole až do této chvíle. Podle ní si můžete zkontrolovat, jestli jsou vaše soubory v pořádku. Kromě toho zde najdete i třídu Strom_3bTest, pomocí níž můžete třídu Strom_3b otestovat. 3.18 Komentáře a dokumentace V této podkapitole (a samozřejmě i v některých dalších) budu občas hovořit současně o atributech, konstruktorech i metodách. V takovém případě budu pro všechny používat termín členy. Kdybychom se tohoto označení drželi, mohli bychom o atributech hovořit jako o datových členech a o metodách a konstruktorech jako o funkčních členech nebo členských funkcích. Současně bychom mohli rozlišovat statické členy a nestatické, neboli instanční členy. Já tyto složené termíny používat nebudu; uvádím je tu jen proto, že se s nimi můžete občas v literatuře setkat. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 148 z 433 Kapitola 3: Vytváříme vlastní třídu 149 Programátorská zkušenost říká, že žádný program nevydrží věčně v podobě, ve které byl původně vyvinut. Prakticky každý program je po kratší či delší době vylepšován a upravován. Naše třída je sice relativně maličká, ale i tak se nám již utěšeně rozrůstá a za chvíli bychom v ní mohli ztratit orientaci, zejména pokud bychom se k ní vrátili po delší době. A jak bychom teprve bloudili v případě, kdyby tato třída byla opravdu velká. Programátoři proto doplňují své programy komentáři, ve kterých si poznamenávají, proč třídu nadefinovali právě takto. Popisují v nich celkový účel dané třídy, funkci jednotlivých metod, význam jejich parametrů a řadu dalších důležitých vlastností. V tělech metod si pak poznamenávají některé nestandardní obraty, které by čtenářům kódu nemuseli být jasné. Z hlediska překladače je komentář něco podobného jako mezera. Kamkoliv můžete v programu vložit mezeru, tam můžete stejně dobře vložit i komentář. Výjimkou je pouze vnitřek textových řetězců, jenže tam mezera nefunguje jako oddělovač jiných prvků programu, ale vystupuje tam jako znak. Tři druhy komentářů Java zavádí tři druhy komentářů: Řádkový komentář začíná dvojicí znaků // a končí spolu s koncem řádku. Řádkové komentáře používáme většinou tehdy, chceme-li doplnit poznámkou nějaký kus kódu uvnitř těla konstruktoru nebo nějaké větší třídy. Obecný komentář začíná dvojicí znaků /* a končí inverzní dvojicí */ – tyto dvojice slouží jako komentářové závorky. Vše, co mezi nimi překladač najde, ignoruje. Obecný komentář proto může zabírat několik řádků. Dokumentační komentář je pouze speciálním typem obecného komentáře, který začíná trojicí znaků /**. Zapisuje se do nich dokumentace k vytvářenému programu. Mezi programy obsaženými v sadě JDK je program javadoc.exe, který umí prohledat označené soubory, vypreparovat z nich dokumentační komentáře a vytvořit z těchto komentářů řádnou dokumentaci. Dokumentační komentáře se v programu zapisují těsně před dokumentovaný prvek (program javadoc reaguje i na umístění jednotlivých komentářů): komentář popisující účel a použití třídy patří před hlavičku třídy, komentář popisující účel nějakého atributu patří před deklaraci tohoto atributu, komentář popisující funkci nějaké metody a význam jejích parametrů patří před hlavičku této metody. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 149 z 433 150 Myslíme objektově v jazyku Java 1.5 Pokračovací řádky obecných a dokumentačních komentářů bývá dobrým zvykem začínat hvězdičkami. Ty sice nejsou povinné, ale zvyšují přehlednost programu, protože výrazně oddělují komentář od výkonného kódu. Proto je řada programátorských editorů (mezi nimi i BlueJ) na počátek pokračovacích řádků automaticky vkládá. Já ve svých programech navíc používám první řádek komentáře tvořený celou řadou hvězdiček, který tak vizuálně odděluje jednotlivé metody. Je na vás, jestli tento zvyk převezmete a nebo si zavedete nějaké vlastní konvence. Vhodné komentáře dokáží výrazně zvýšit čitelnost programu. Naučte se své programy dostatečně komentovat, abyste pak při pozdějších úpravách nemuseli pracně analyzovat, jak jste to tenkrát mysleli. Znovu bych zde zopakoval, že dobrý programátor píše programy, kterým rozumí nejen počítač, ale i člověk. Správné komentování programů proto patří k dobrým programátorským mravům. Vyzkoušíme si nyní použití komentářů na našem programu. V programu provedeme následující úpravy: Před celou třídu, který bude popisovat účel třídy. Před každou z metod vložíme dokumentační komentář popisující funkci dané metody. Dokumentační komentáře s popisem významu vložíme i před deklarace veřejných atributů. Soukromé atributy doplníme řádkovými komentáři popisujícími jejich použití. Jednotlivé deklarace a definice v těle třídy navíc proložíme řádkovými komentáři, které nám celé tělo rozdělí na sekce obsahující deklarace a definice stejného druhu objektů. Mezi definice metod vložíme pro zvýšení přehlednosti dva prázdné řádky, za poslední deklaraci či definici před oddělujícím řádkovým komentářem vložíme tři prázdné řádky. (V následujícím programu je pro úsporu místa vždy o jeden oddělující řádek méně.) Za zavírací závorku těla třídy vložíme řádkový komentář, do nějž zkopírujeme hlavičku třídy. (Tuto grafickou úpravu využívám k tomu, abych měl jasně označenu závěrečnou zavírací závorku pro případ, kdyby počet otevíracích a zavíracích závorek v programu nesouhlasil.) Program Strom by po všech těchto úpravách (a drobné změně pořadí jednotlivých metod) mohl vypadat následovně: @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 150 z 433 Kapitola 3: Vytváříme vlastní třídu 151 Abych nemusel program za chvíli ukazovat ještě jednou, použil jsem v něm i komentářové značky začínající znakem @. Podrobnosti o těchto značkách si povíme za chvíli v pasáži Pomocné značky pro tvorbu dokumentace na straně 164. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 /******************************************************************************* * Třída Strom obsahuje podobu třídy po zavedení komentářů. * * Oproti předchozí verzi je zdrojový kód pouze okomentován. * Obsahuje dokumentační komentáře všech veřejných atributů a tříd. * Atributy a metody jsou navíc seřazeny podle doporučeného pořadí. * * @author Rudolf Pecinovský * @version 2.01, duben 2004 */ public class Strom { //== KONSTANTNÍ ATRIBUTY TŘÍDY ================================================= /** Udává kolikrát je strom vyšší než samotný kmen. */ public static final int IMPLICITNÍ_POMĚR_VÝŠKY = 3; /** Udává kolikrát je koruna širší než kmen. */ public static final int IMPLICITNÍ_POMĚR_ŠÍŘKY = 10; //== PROMĚNNÉ ATRIBUTY TŘÍDY =================================================== /** Velikost posunu pro bezparametrické posunové metody. */ private static int krok = 50; //== KONSTANTNÍ ATRIBUTY INSTANCÍ ============================================== private final Elipsa koruna; private final Obdélník kmen; //Koruna stromu //Kmen stromu //== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================ //== PŘÍSTUPOVÉ METODY ATRIBUTŮ TŘÍDY ========================================== /*************************************************************************** * Vrátí velikost implicitního kroku, o který se instance přesune * při volaní bezparametrickych metod přesunu. * * @return Velikost implicitního kroku v bodech */ public static int getKrok() { return krok; } /*************************************************************************** * Nastaví velikost implicitního kroku, o který se instance přesune @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 151 z 433 152 Myslíme objektově v jazyku Java 1.5 * při volaní bezparametrickych metod přesunu. 50 * 51 * @param velikost Velikost implicitního kroku v bodech;<br/> 52 * musí platit: 0 <= velikost <= 100 53 */ 54 public static void setKrok( int velikost ) 55 { 56 krok = velikost; 57 } 58 59 /*************************************************************************** 60 * Metoda se dotáže uživatele na požadovanou velikost kroku používaného 61 * v bezparametrických posunových metodách a zadanou hodnotu nastaví. 62 */ 63 public static void setKrok() 64 { 65 krok = P.zadej( "Zadejte novou velikost kroku:", krok ); 66 } 67 68 69 70 //== OSTATNÍ METODY TŘÍDY ====================================================== 71 /*************************************************************************** 72 * Vytvoří instanci zadané velikosti a upraví rozměr plátna tak, 73 * aby byla na plátně právě zarámovaná. 74 */ 75 public static void zarámuj( int šířka, int výška ) 76 { 77 Plátno.getPlátno().setRozměr( šířka, výška ); 78 new Strom( 0, 0, šířka, výška ); 79 } 80 81 /*************************************************************************** 82 * Metoda upraví rozměr plátna a "vysadí" na něj alej dvou řad stromů 83 * se třemi stromy v každé řadě. Stromy budou vysazeny šikmo ve směru 84 * hlavní diagonály s kmenem zabírajícím 1/3 výšky a 1/10 šířky stromu. 85 */ 86 public static void alej() 87 { 88 Plátno plátno = Plátno.getPlátno(); 89 plátno.setRozměr( 400, 350 ); 90 91 new Strom( 100, 0 ); new Strom( 300, 0 ); new Strom( 50, 100 ); new Strom( 250, 100 ); 92 new Strom( 0, 200 ); new Strom( 200, 200 ); 93 } 94 95 96 97 //== KONSTRUKTORY ============================================================== 98 /*************************************************************************** 99 * Implicitní konstruktor třídy Strom vytvoří v levém horním rohu plátna 100 * instanci širokou 100 bodů, vysokou 150 bodů 101 * s kmenem zabírajícím 1/3 výška a 1/10 šířky stromu. 102 */ 103 public Strom() 104 105 { this( 0, 0 ); 106 } 107 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 152 z 433 Kapitola 3: Vytváříme vlastní třídu 153 108 /*************************************************************************** 109 * Vytvoří na zadaných souřadnicích 110 * instanci širokou 100 bodů, vysokou 150 bodů 111 * s kmenem zabírajícím 1/3 výška a 1/10 šířky stromu. 112 * 113 * @param x x-ová souřadnice počátku, x>=0, x=0 má levý okraj plátna 114 * @param y y-ová souřadnice počátku, y>=0, y=0 má horní okraj plátna 115 */ 116 public Strom( int x, int y ) 117 { 118 this( x, y, 100, 150 ); 119 } 120 121 /*************************************************************************** 122 * Vytvoří na zadaných souřadnicích instanci se zadanou šířkou a výškou. 123 * Poměr velikosti kmene ku zbytku stromu zůstane implicitní, tj. 124 * kmen bude zabírat 1/3 výška a 1/10 šířky stromu. 125 * 126 * @param x x-ová souřadnice počátku, x>=0, x=0 má levý okraj plátna 127 * @param y y-ová souřadnice počátku, y>=0, y=0 má horní okraj plátna 128 * @param šířka Šířka vytvářené instance, šířka > 0 129 * @param výška Výška vytvářené instance, výška > 0 130 */ 131 public Strom( int x, int y, int šířka, int výška ) 132 { 133 this( x, y, šířka, výška, 134 IMPLICITNÍ_POMĚR_ŠÍŘKY, IMPLICITNÍ_POMĚR_VÝŠKY ); 135 } 136 137 /*************************************************************************** 138 * Vytvoří na zadaných souřadnicích instanci se zadanou šířkou, výškou. 139 * a poměrem velikosti kmene ku zbytku stromu. 140 * 141 * @param x x-ová souřadnice počátku, x>=0, x=0 má levý okraj plátna 142 * @param y y-ová souřadnice počátku, y>=0, y=0 má horní okraj plátna 143 * @param šířka Šířka vytvářené instance, šířka > 0 144 * @param výška Výška vytvářené instance, výška > 0 145 * @param podílVýškyKmene Kolikrát je kmen menší než celý strom 146 * @param podílŠířkyKmene Kolikrát je kmen užší než celý strom 147 */ 148 149 public Strom( int x, int y, int šířka, int výška, int podílVýškyKmene, int podílŠířkyKmene ) 150 { 151 int výškaKmene = výška / podílVýškyKmene; 152 153 int výškaKoruny = výška - výškaKmene; int šířkaKmene = šířka / podílŠířkyKmene; 154 155 int posunKmene = ( šířka - šířkaKmene) / 2; 156 koruna = new Elipsa ( x, y, šířka, výškaKoruny, Barva.ZELENÁ ); kmen = new Obdélník( x+posunKmene, y+výškaKoruny, 157 šířkaKmene, výškaKmene, Barva.ČERVENÁ ); 158 } 159 160 161 162 //== PŘÍSTUPOVÉ METODY ATRIBUTŮ INSTANCÍ ======================================= 163 /*************************************************************************** 164 * Vrátí x-ovou souřadnici pozice instance. 165 @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 153 z 433 154 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 Myslíme objektově v jazyku Java 1.5 * * @return x-ová souřadnice. */ public int getX() { return xPos; } /*************************************************************************** * Vrátí y-ovou souřadnici pozice instance. * * @return y-ová souřadnice. */ public int getY() { return yPos; } /*************************************************************************** * Nastaví novou pozici instance. * * @param x Nová x-ová pozice instance * @param y Nová y-ová pozice instance */ public void setPozice(int x, int y) { { koruna.setPozice( x, y ); kmen .setPozice( x + (koruna.getŠířka() - kmen.getŠířka()) / 2, y + koruna.getVýška() ); koruna.nakresli(); } /*************************************************************************** * Vrátí šířku instance. * * @return Šířka instance v bodech */ public int getŠířka() { return koruna.getŠířka(); } /*************************************************************************** * Vrátí výšku instance. * * @return Výška instance v bodech */ public int getVýška() { return koruna.getVýška() + kmen.getVýška(); } /*************************************************************************** * Vrátí barvu koruny stromu. * * @return Instance třídy Barva definující nastavenou barvu koruny. */ @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 154 z 433 Kapitola 3: Vytváříme vlastní třídu 155 public Barva getBarvaKoruny() 224 { 225 return koruna.getBarva(); 226 } 227 228 /*************************************************************************** 229 * Nastaví novou barvu koruny. 230 * 231 * @param nová Požadovaná nová barva. 232 */ 233 public void setBarvaKoruny( Barva nová ) 234 { 235 koruna.setBarva( nová ); 236 } 237 238 239 240 //== OSTATNÍ METODY INSTANCÍ =================================================== 241 /*************************************************************************** 242 * Vykreslí obraz své instance na plátno. 243 */ 244 public void nakresli() 245 { 246 koruna.nakresli(); 247 kmen .nakresli(); 248 } 249 250 /*************************************************************************** 251 * Smaže obraz své instance z plátna (nakreslí ji barvou pozadí plátna). 252 */ 253 public void smaž() 254 { 255 koruna.smaž(); 256 kmen .smaž(); 257 } 258 259 /*************************************************************************** 260 * Přesune instanci o zadaný počet bodů vpravo, 261 * při záporné hodnotě parametru vlevo. 262 * 263 * @param vzdálenost Vzdálenost, o kterou se instance přesune. 264 266 */ public void posunVpravo( int vzdálenost ) 267 { 268 koruna.posunVpravo( vzdálenost ); 269 270 kmen .posunVpravo( vzdálenost ); } 271 272 273 /*************************************************************************** * Přesune instanci o krok bodů vpravo. 274 */ 275 public void posunVpravo() 276 { 277 posunVpravo( krok ); 278 } 279 280 /*************************************************************************** 281 * Přesune instanci o krok bodů vlevo. 282 @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 155 z 433 156 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 }// Myslíme objektově v jazyku Java 1.5 */ public void posunVlevo() { posunVpravo( -krok ); } /*************************************************************************** * Přesune instanci o zadaný počet bodů dolů, * při záporné hodnotě parametru nahoru. * * @param vzdálenost Počet bodů, o které se instance přesune. */ public void posunDolů( int vzdálenost ) { koruna.posunDolů( vzdálenost ); kmen .posunDolů( vzdálenost ); koruna.nakresli(); } /*************************************************************************** * Přesune instanci o krok bodů dolů. */ public void posunDolů() { posunDolů( krok ); } /*************************************************************************** * Přesune instanci o krok bodů nahoru. */ public void posunVzhůru() { posunDolů( -krok ); } /*************************************************************************** * Nastaví parametry okna s plátnem tak, aby právě zarámovalo danou * instanci. Instanci před tím přesune do levého horního rohu plátna. */ public void zarámuj() { Plátno.getPlátno().setRozměr( getŠířka(), getVýška() ); setPozice( 0, 0 ); } public class Strom Zdrojové kódy předchozí třídy najdete v projektu 03_Třídy_Z ve třídě Strom_3c. Podle ní si můžete zkontrolovat, jestli jsou vaše soubory v pořádku. Kromě toho zde najdete i třídu Strom_3cTest, pomocí níž můžete třídu Strom_3c otestovat. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 156 z 433 Kapitola 3: Vytváříme vlastní třídu 157 Uspořádání jednotlivých prvků v těle třídy Jak vidíte, už je to docela pěkný macek a další programy budou ještě větší. Je proto nanejvýš vhodné zavést nějaké konvence, abyste potřebné členy ve zdrojovém souboru co nejrychleji našli. Ve všech programech této učebnice bude proto tělo třídy rozděleno do oddílů, které budou odděleny jednořádkovými komentáři označujícími obsah následujícího oddílu a které budou mít ve všech dalších programech pevné pořadí. Přitom budu vycházet z následujících zásad: Nejprve budou deklarovány atributy a teprve po nich metody. Jak mezi atributy, tak mezi metodami dáme vždy dopředu statické členy a teprve za nimi budou členy nestatické. Konstanty budou uvedeny dříve než proměnné. Konstruktory budou umístěny uprostřed mezi statickými a nestatickými metodami. Statické metody budou před nimi proto, že k jejich zavolání není potřeba mít definovanou žádnou instanci, a nestatické za nimi, protože před jejich použitím je potřeba nejprve použít konstruktor, který nějakou instanci vytvoří. Mezi konstruktory zařadíme i metody, které vracejí odkaz na instanci vlastní třídy (sem patří např. metoda getPlátno). K těmto metodám se ještě vrátím v kapitole Jedináček (Singleton) na straně 233. Mezi metodami uvedeme vždy nejprve metody, které nastavují nebo vracejí hodnoty atributů (byť zdánlivých) a teprve za nimi ostatní metody. V programu jsou tedy sekce uspořádané v následujícím pořadí: 1. konstantní atributy třídy, tj. statické konstanty, 2. ostatní atributy třídy, 3. konstantní atributy instancí, 4. ostatní atributy instancí, 5. přístupové metody atributů třídy, 6. ostatní metody třídy, 7. konstruktory a metody vracející odkaz na instance vlastní třídy, 8. přístupové metody atributů instancí, 9. ostatní metody instancí. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 157 z 433 158 Myslíme objektově v jazyku Java 1.5 Sekce s ostatními metodami instancí je ještě podrobněji dělena, ale toto dělení si probereme až poté, co si vysvětlíme příslušné konstrukce (budeme se s nimi postupně seznamovat v průběhu celé následující části). Prozatím budeme používat pouze „Nově zavedené metody instancí“. BlueJ a komentářová nápověda BlueJ je schopen využít dokumentačních komentářů jako nápovědy při volání konstruktorů a metod. Jak víte, voláte-li nějaký konstruktor nebo metodu, která očekává parametry, otevře BlueJ dialogové okno, ve kterém zadáte názve odkazu na vytvořenou instanci a/nebo hodnoty parametrů. Do horní části tohoto dialogového okna BlueJ opíše dokumentační komentář. Obrázek 3.29 Při volání konstruktorů a metod s parametry se do dialogového okna opíše dokumentační komentář Dokumentační komentáře však neslouží pouze k tomu, aby se jejich obsah vypsal do nějakých dialogových oken. Ony slouží opravdu k vytvoření dokumentace. O tom se můžete přesvědčit např. tak, že rozbalíte seznam na pravém okraji panelu s tlačítky a místo dosavadní položky Implementace (zdrojový kód) vyberete položku Dokumentace (popis rozhraní) (viz obr. 3.30). BlueJ pak zavolá program, který je součástí JDK a který z vašich dokumentačních komentářů vyrobí HTML soubor efektně dokumentující vaši třídu (viz obr. 3.31). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 158 z 433 Kapitola 3: Vytváříme vlastní třídu 159 Obrázek 3.30 Editor umožňuje přepínat mezi zobrazením kódu a vygenerované dokumentace @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 159 z 433 160 Myslíme objektově v jazyku Java 1.5 Obrázek 3.31 Z dokumentačních komentářů se přímo vygeneruje profesionální dokumentace Automaticky generovaná dokumentace Tato autodokumentační schopnost Javy je jednou z jejích velice příjemných vlastností. Při generování dokumentace však Java (přesněji program javadoc) nezůstává pouze u toho, že někam opíše obsah dokumentačních komentářů. Jak si můžete na vygenerovaném souboru ověřit, vygenerovaná dokumentace sestává z několika částí: 1. Popis vlastností a použití dané třídy získaný z dokumentačního komentáře před hlavičkou třídy. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 160 z 433 Kapitola 3: Vytváříme vlastní třídu 161 2. Tabulky, které shrnou základní informace o dostupných atributech, konstruktorech a metodách, přičemž každá skupina prvků má vlastní tabulku. U každého členu je uvedena pouze první věta z jeho komentáře (viz např. komentář čtyřparametrického konstruktoru nebo metody alej()) a prvky jsou v tabulkách řazeny abecedně. 3. Úplné komentáře o všech atributech, konstruktorech a metodách. BlueJ zařídí i u těchto popisů jejich seřazení podle abecedy, ale v praxi bývají tyto popisy většinou seřazeny ve stejném pořadí, v jakém jsou uvedeny v programu. I tato část je rozdělená na sekce věnované postupně atributům, konstruktorům a metodám. Takto vygenerovaná dokumentace je uložena do souborů ve formátu HTML v podsložce doc složky, ve které máte uložen projekt. Jednotlivé části dokumentace jsou spolu provázané hypertextovými odkazy, jež jsou jednou z příjemných vlastností formátu HTML. Tu podporuje i editor prostředí BlueJ. Klepnete-li v tabulce atributů, konstruktorů nebo metod na název vybraného objektu, přesunete se automaticky do sekce, kde si můžete přečíst jeho podrobnou dokumentaci. Editor však, na rozdíl od webových prohlížečů, nenabízí jednoduchou možnost vrátit se zpět na místo, odkud jste sem skočili. Všimněte si, že v dokumentaci jsou uvedeny pouze členy deklarované jako veřejné. Neobjevují se v ní proto např. soukromé atributy. Kdybyste některou z metod označili jako soukromou, v následně vygenerované dokumentaci by se již také neobjevila (vyzkoušejte si to). Dokumentace celého projektu V prostředí BlueJ nemusíte vytvářet dokumentaci každé třídy zvlášť, ale můžete BlueJ požádat, ať vám vygeneruje dokumentaci celého projektu. Toho dosáhnete klávesovou zkratkou CTRL+J nebo zadáním příkazu Nástroje → Dokumentace projektu v aplikačním okně daného projektu (viz obr. 3.32). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 161 z 433 162 Myslíme objektově v jazyku Java 1.5 Obrázek 3.32 BlueJ můžete požádat i o vytvoření dokumentace k celému projektu Dokumentace projektu se již neotevírá v okně editoru, ale v okně vašeho prohlížeče, protože se v ní používají rámy: v levém je zobrazen seznam všech tříd v projektu, v pravém pak dokumentace konkrétní třídy (viz obr. 3.33). Klepnutím na název třídy v levém rámu otevřete její dokumentaci v rámu pravém. Výhodou otevření dokumentace v prohlížeči je i to, že nyní můžete svobodně využívat všech možností hypertextových odkazů včetně možnosti návratu do místa, odkud jsme sem skočili nebo otevření cíle v novém okně. Uložíte-li si navíc tuto stránku mezi své oblíbené webové stránky, budete ji moci otevřít kdykoliv si vzpomenete nezávisle na tom, máte-li zrovna otevřené prostředí BlueJ či nikoliv. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 162 z 433 Kapitola 3: Vytváříme vlastní třídu 163 Obrázek 3.33 Dokumentace projektu se otevírá v okně prohlížeče, protože obsahuje rám se seznamem tříd a rám s dokumentací vybrané třídy Dokumentace projektu obsahuje v záhlaví řadu dalších odkazů, které vám umožní se po ní snadno pohybovat. Jejich funkci tu nebudu rozebírat. Jste-li zvídaví, vyzkoušejte si ji. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 163 z 433 164 Myslíme objektově v jazyku Java 1.5 Pomocné značky pro tvorbu dokumentace Podíváte-li se podrobněji na dokumentaci metod z předdefinovaných tříd, zjistíte, že obsahuje některé dodatečné informace, o něž je dokumentace třídy Strom prozatím ochuzena. Podíváte-li se např. na podrobný popis konstruktorů a metod s parametry, najdete zde sekci Parameters: s popisem jednotlivých parametrů. Obdobně podíváte-li se na podrobný popis metod vracejících nějakou hodnotu, najdete zde sekci Returns: s popisem vracené hodnoty. Generaci těchto sekcí vyvolají tzv. dokumentační značky, které jsou součástí příslušných dokumentačních komentářů. Dokumentačních značek je velké množství. My se zde seznámíme pouze s několika z nich. Až budete zkušenější, vaše znalosti značek opět rozšíříme. Nebudu zde uvádět ani příklady použití dále popsaných značek – najdete je ve zdrojových textech předdefinovaných tříd. Každá z následně uvedených značek musí být na počátku řádku (může ale stát za úvodní hvězdičkou). Vztahuje se k ní veškerý zbylý text na řádku spolu s texty na dalších řádcích až do řádku s příští značkou nebo do konce komentáře. @author Tato značka se uvádí v dokumentaci k celé třídě. Zapisuje se za ní název autora dané třídy. @version I tato značka se uvádí v dokumentaci k celé třídě. Zapisuje se za ní číslo verze. Pro formát verze není žádný předpis – můžete jej zadat třeba slovně i s popisem změn oproti verzi minulé. @param Značka, kterou použijete v dokumentaci konstruktorů a metod s parametry. Za ní je třeba uvést přesný název parametru z hlavičky příslušného konstruktoru či metody a za něj pak jeho popis. @returns Tuto značku použijete v dokumentaci metod, které vracejí nějakou hodnotu. Uvádí se za ní podrobný popis návratové hodnoty. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 164 z 433 Kapitola 3: Vytváříme vlastní třídu 165 3.19 Závěrečný příklad – UFO Do této doby jsme si stále jenom ukazovali, jaké má to či ono vlastnosti, jak se to naprogramuje případně kde nás čeká nějaká záludnost. Samé učení a žádná zábava. Na závěr kapitoly jsem proto pro vás připravil velkou závěrečnou úlohu, při které si ověříte, že jste se toho již opravdu hodně naučili a zároveň se možná i trochu pobavíte. Naprogramujete si totiž vlastní jednoduchou hru. Nenaprogramujete ji sice celou, ale pouze jednu její část, nicméně i v praxi bývá hlavním úkolem programátora vytvořit nějakou část rozsáhlejšího projektu. Připravil jsem pro vás projekt 03_UFO, ve kterém najdete pět připravených tříd, jednu prázdnou třídu pro svoje řešení a jednu třídu se vzorovým řešením pro ty, kteří se dostanou do nějakých problémů. Projekt obsahuje následující třídy: Vesmír – její instance má na starosti vytvoření a zobrazení aplikačního okna, představujícího vesmír, v němž se celá hra odehrává. Tato třída je v aplikaci jako služební a vaše třída s ní komunikovat nebude. Barva – třída, kterou znáte z naší aplikace pracující s grafickými objekty. Dispečer – klíčová třída celé aplikace. její instance má na starosti otevření příslušného vesmíru a řízení veškerého provozu v něm. Tuto instanci vždy požádáte o přistavení nového UFO na startovací rampu, odkud je máte dovést do některého z hangárů. Talíř – představuje jednu z částí UFO. Nabízí jedinou metodu, pomocí které můžete nastavit jeho pozici. Číslo – instance této třídy je schopna zobrazit číslo, které jí zadáte při konstrukci. Instance této třídy slouží k identifikaci UFO a přistávacích ramp (hangárů). Instance třídy Číslo sice nabízejí metod více, ale vy nejspíš použijete pouze metodu pro nastavování jejich pozice. UFO_RUP – třída se vzorovým řešením. Jako obyčejně vám doporučuji, abyste se do ní podívali až v případě, kdy budete mít nějaké problémy nebo když budete hotoví a budete chtít porovnat svoje řešení se vzorovým. UFO – třída, kterou máte definovat. Obsahuje komentované prázdné definice všech metod, které musíte vytvořit. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 165 z 433 166 Myslíme objektově v jazyku Java 1.5 Třída Dispečer Jak jsem již řekl, třída Dispečer je klíčovou třídou celé aplikace. Chová se obdobně jako třída Plátno – protože chce, aby její instance byla jedináček, nenabídne vám konstruktor, ale pouze metodu getDispečer(), která vrátí odkaz na aktuálního dispečera. Pokud ještě dispečer ještě není vytvořen, tak jej vytvoří spolu s vesmírem, který bude tímto dispečerem ovládán. Dispečer může pracovat ve dvou režimech a tak ovládat dvě varianty hry. V první jednodušší variantě ovládáte svá UFO prostřednictvím BlueJ. tj. obdobně, jako jste doposud ovládali Strom a další grafické tvary, v druhé variantě ovládáte pohyb UFO a vůbec celou hru přímo z klávesnice. Jednodušší varianta Při této variantě si musíte uložit do zásobníku odkaz na vytvořeného dispečera, abyste jej mohli vždy požádat o přistavení nového UFO na startovací rampu. Žádáte jej zasláním zprávy přistavUFO(), po níž dispečer přistaví na rampu nové UFO vrátí odkaz na něj. Získaný odkaz uložíte do zásobníku odkazů a vhodným zadáváním rychlosti pomocí zprávy setRychlost(x,y) ovládáte rychlost UFO a snažíte se je zaparkovat v nějakém hangáru. Vjedete-li do hangáru dostatečně pomalu, měly by přistávací mechanizmy zabezpečit jeho automatické zaparkování. Aby se vám podařilo UFO zaparkovat, nesmí mít při vjezdu do hangáru větší součet rychlostí ve vodorovném a svislém směru, než je velikost jeho talíře, která je přednastavena na 20 bodů. (Když ji ve třídě Talíř změníte, změníte tím i rozměry celého vesmíru). Jestli jste šikovní, můžete si vyzkoušet, zda dokážete ovládat několik UFO současně. Odhadnete-li dobře rychlost, abyste ji nemuseli následně korigovat, můžete zkusit, kolik UFO se vám podaří poslat najednou. Varianta ovládaná z klávesnice Zvolíte-li variantu ovládanou z klávesnice, musíte nejprve aktivovat okno vytvořeného vesmíru (třeba tím, že na něj klepnete myší). Nebude-li okno aktivní, nebude na stisky kláves reagovat. Při této variantě ovládáte kurzorovými klávesami tah motorů. Stisknete-li např. klávesu se šipkou doprava, začne UFO zrychlovat doprava. S každým dalším stiskem zvyšujete zrychlení. Stiskem mezerníku vypínáte motory. Od té chvíle se bude UFO pohybovat konstantní rychlostí (ve vesmíru není tření). Budete-li je chtít zabrzdit, musíte za- @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 166 z 433 Kapitola 3: Vytváříme vlastní třídu 167 pnout motory v opačném směru. Nesmíte je však zapomenout zase vypnout, až se UFO zastaví, jinak vám bude pokračovat ve zrychlování opačným směrem. O přistavení dalšího UFO požádáte stiskem klávesy ENTER. Na klávesnici reaguje vždy naposledy přistavené UFO. Budete-li ovládat několik UFO současně, zapnete ovládání příslušného UFO stiskem klávesy s jeho číslem. Třída UFO Součástí projektu je i třída UFO, která má sice definovány všechny potřebné metody, ale má je definovány jako prázdné (tj. s prázdným tělem, jehož definice je vaším úkolem). Každá z těchto prázdných metod je doplněna dokumentačním komentářem, v němž je popsána její požadovaná funkce. Řekl bych, že naprogramování většiny z nich by vám nemělo dělat potíže. Pro jistotu si ale zopakujeme trochu fyziky, abychom si ujasnili, jak by měla fungovat metoda popojeď(int), jejíž funkce by mohla dělat některým z vás problémy. Ze své zkušenosti vím, že většina začínajících studentů programování by ráda programovala počítačové hry, ve kterých se to bude hemžit animovanými předměty. Patříte-li mezi ně, mohl by být pro vás následující drobný teoretický výklad užitečný. Ve škole jste se učili, že rychlost je definována jako dráha uražená za jednotkový čas. Rychlost animovaných předmětů má dvě složky: vodorovnou a svislou. Vodorovná složka rychlosti říká, jak rychle se předmět posouvá ve vodorovném směru a svislá složka popisuje rychlost ve směru svislém. Má-li tedy nějaký animovaný předmět definovanou rychlost jako počet bodů, o které se má posunout za sekundu, změní se jeho pozice každou sekundu o požadovaný počet bodů. Budeme-li však chtít, aby se předmět posouval relativně plynule, nemůžeme jeho pozici měnit jednou za sekundu, musíme ji měnit častěji. U animovaných obrázků se často udává frekvence jejich překreslování. Ta říká, kolikrát se obrázek za sekundu překreslí. Má-li se tedy předmět přesouvat ve vodorovném směru rychlostí rx bodů za sekundu, musí se jeho pozice změnit mezi dvěma překresleními o rx/frekvence bodů. O našich UFO jsme si říkali, že jsou ovládána raketovými motory, které neovládají přímo jejich rychlost, ale řídí jejich zrychlení. Jsou-li motory vypnuty, pohybuje se raketa ve vesmíru stále stejnou rychlostí (na rozdíl od raket, které známe z filmů, jejichž tvůrci neznají fyziku). Jakmile motory zapnu, budu raketa neustále zrychlovat či zpomalovat. Zůstane-li brzdící motor zapnut i poté, co raketu zastavím, stane se z něj motor urychlovací a raketa začne zrychlovat opačným směrem. Se zrychlením je to obdobně jako s rychlostí. Zrychlení je definováno jako změna rychlosti za jednotku času. Má-li předmět zrychlovat ve vodorovném smě@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 167 z 433 168 Myslíme objektově v jazyku Java 1.5 ru se zrychlením zx, musí se jeho vodorovná rychlost změnit mezi dvěma překresleními obrazovky o zx/frekvence. Označíme-li vodorovnou pozici objektu jako px, budeme při nenulovém zrychlení muset pří výpočtu jeho budoucí pozice počítat se stále se zvyšující rychlostí, takže tuto pozici zjistíme následujícího dvojicí příkazů: rx = rx + (zx / frekvence); px = px + (rx / frekvence); Obdobné to bude i se svislou pozicí, rychlostí a zrychlením. Myslete na to při definici metody popojeď(int), která má na starosti přesun vašeho UFO mezi jednotlivými překresleními vesmíru. Třída UFOTest Abyste mohli průběžně kontrolovat svůj návrh třídy UFO, připravil jsem třídu UFOTest, která obsahuje několik testovacích metod. Metoda testUFO() postupně prověří většinu metod instanci třídy UFO. Testuje konstruktor, metody pro zjištění jeho tahu, rychlosti a pozice a funkci metod setRychlost(int,int) a popojeď(int). Metoda testRychlost() otestuje také schopnost metody zobraz(). Nechá vytvořit dispečera, pověří jej přistavením pěti UFO a ta pak doprovodí do jejich hangárů. Bude-li všechno fungovat jak má, mělo by na konci být v každém hangáru zaparkované jedno UFO. Metoda testHra() vytvoří dispečera (a s ním i příslušný vesmír) a spustí tak hru. Stejného výsledku však dosáhnete i tím, že třídě Dispečer pošlete zprávu getDispečer(). 3.20 Vytvoření samostatné aplikace Když už jste si vytvořili vlastní aplikaci, ukážeme si, jak lze zařídit, abyste ji mohli spouštět stejně jako ostatní aplikace a nepotřebovali jste k jejímu spuštění otevírat nejprve BlueJ. Java umožňuje zapakovat celou aplikaci do speciálního souboru a ten pak spouštět obdobně, jako se spouští standardní spustitelné soubory. Jedinou podmínkou pro jeho spuštění je instalovaná Java. Nemusí to ale být kompletní sada pro vývojáře (SDK), kterou jste si museli instalovat vy, ale stačí jenom běhové @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 168 z 433 Kapitola 3: Vytváříme vlastní třídu 169 prostředí JRE (Java Runtime Environment), které je výrazně menší (14,5 MB oproti 50 MB SDK). Tento zapakovaný soubor má příponu JAR, což je zkratka z Java ARchive. JAR soubor je vlastně obyčejný ZIP soubor, který obsahuje kromě přeložených tříd dané aplikace ještě složku META-INF se souborem MANIFEST.MF, v němž jsou uloženy některé informace důležité pro spuštění souboru. Třída spouštějící aplikaci Aplikace, která má být spustitelná ze systému, musí obsahovat třídu s veřejnou statickou metodou main, která má parametr typu String[] (co znamenají ty hranaté závorky se dozvíte ve třetí části učebnice). Tuto metodu můžete dodat do kterékoliv ze svých tříd, ale často bývá nejjednodušší vytvořit speciální třídu, která nebude mít na starosti nic jiného než spuštění vaší aplikace. Svoji aplikaci však můžete o spouštěcí třídu jednoduše doplnit – stačí požádat o vytvoření nové třídy a v následně otevřeném dialogovém okně nastavit přepínač typu vytvářené třídy na Třída spouštějící aplikaci. Název této třídy může být libovolná, ale doporučuji vám, abyste si zavedli nějakou konvenci, kterou budete v budoucnu dodržovat a tím si zjednodušíte orientaci v programu. Tuto třídu můžete např. nazývat Hlavní nebo Aplikace. Já ji budu ve zbytku této učebnice nazývat Hlavní, protože musí povinně obsahovat metodu main. Vy se ale můžete rozhodnout podle svého. Otevřete zdrojový kód nově vytvořené spouštěcí třídy a doplňte do její metody main potřebný kód, kterým budete aplikaci spouštět. V našem případě je tímto kódem vyvolání metody Dispečer.getDispečer(). 1 public class Hlavní 2 { 3 public static void main( String [] args ) 4 { 5 /*# Sem vložte kód, kterým se bude spouštět celá aplikace */ Dispečer.getDispečer(); 6 7 } 8 }//public class Hlavní Celý projekt pak přeložte a tím jste připraveni k druhému kroku. Vytvoření souboru JAR s aplikací Vytvoření spustitelného souboru je pak jednoduché: 1. Zadejte příkaz Projekt → Export…. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 169 z 433 170 Myslíme objektově v jazyku Java 1.5 2. V následně otevřeném dialogovém okně BlueJ: Exportovat projekt (viz obr. 3.34) zadejte: Přepínač nastavte do polohy Uložit do jar souboru. V rozbalovacím seznamu Třída s metodou main: vyhledejte třídu, která bude zodpovědná za spuštění aplikace (v našem příkladu je to třída Hlavní). Nastavte zaškrtnutí políčka Přidružit zdrojové soubory podle svých požadavků. Zaškrtnete-li je, přidají se do archivu i zdrojové soubory. Archiv pak bude sice větší, ale budete z něj moci zdrojové soubory kdykoliv vyjmout a upravit. Nezaškrtnete-li je, přijdete o možnost budoucího upravování zdrojových souborů, ale získáte zase menší archiv. Prozatím bych vám doporučoval dát přednost kratšímu souboru a políčko nezaškrtávat. Obrázek 3.34 Dialogové okno BlueJ: Exportovat projekt 3. Stiskněte tlačítko Pokračovat. Otevře se dialogové okno Specify name for jar file, ve kterém zadáte umístění a název ukládaného souboru. Název souboru můžete zadat bez přípony JAR. Teoreticky nemusí dodržovat ani pravidla pro názvy identifikátorů a stačí, bude-li to platný název souboru. Myslete však na to, že budete-li chtít poslat tento soubor kamarádům používajícím jinou platformu, mohli by mít s některými názvy problémy. Proto bych doporučoval zůstat u znakové sady pro identifikátory. 4. Vyzkoušejte, že váš program opravdu funguje jako normální aplikace. Nebude-li jej možno spustit poklepáním nebo jiným ze způsobů, na které jste zvyklí, nebudete mít asi správně asociovánu příponu jar. Pokud jste však instalovali Javu standardním způsobem, bude součástí instalace a správně asociování přípony. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 170 z 433 Kapitola 3: Vytváříme vlastní třídu 171 3.21 Shrnutí – co jsme se v kapitole naučili Deklarací nazýváme oznámení toho, že v dalším programu budu používat nějakou proměnnou, atribut či metodu. Při té příležitosti oznamuji i vlastnosti této metody, proměnné či atributu, aby mne mohl překladač kontrolovat, jestli můj program není s těmito deklarovanými vlastnostmi v rozporu. Definicí se nazývá část programu, která specifikuje nejenom vlastnosti dané entity, ale definitivně ji v programu zavádí, tj. vyhrazuje pro ni paměť a přiřazuje jí počáteční hodnotu. Proměnná je proto definována v okamžiku, kdy jej jí přiřazena počáteční hodnota, metoda je definována v místě, kde je definováno její tělo. Velbloudí notací označujeme způsob zápisu, při němž se několikaslovný název píše dohromady bez mezer jako jedno slovo, přičemž každé slovo názvu začíná velkým písmenem a ostatní písmena jsou malá – např. StrčPrtstSkrzKrk. Názvy tříd píšeme podle konvence velbloudí notací s prvním písmenem velkým – např. StrčPrtstSkrzKrk nebo TřídaSDlouhýmNázvem. Názvy atributů, proměnných a metod píšeme velbloudí notací s prvním písmenem malým – např. strčPrtstSkrzKrk nebo atributSDlouhýmNázvem. Nová třída se vytvoří klepnutím na tlačítko Nová třída a zadáním názvu a druhu vytvářené třídy v následně otevřeném dialogovém okně. Zdrojový kód veřejné třídy je uložen v souboru, který má stejný název jako třída (musí se dodržet i velikost písmen) a má příponu .java. Třídu odstraníme zadáním příkazu Odstranit v místní nabídce třídy. Definice třídy sestává z hlavičky a těla. Hlavička obsahuje modifikátory blíže specifikující vlastnosti třídy (prozatím známe jen modifikátor public), klíčové slovo class a název třídy. Tělo následuje za hlavičkou, je uzavřeno ve složených závorkách a obsahuje definice všech atributů a metod dané třídy. Po každé úpravě zdrojového kódu je třeba třídu před jejím prvním použitím přeložit. Proces, při němž hledáme a odstraňujeme chyby v programu se nazývá ladění. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 171 z 433 172 Myslíme objektově v jazyku Java 1.5 V programech rozeznáváme tři druhy chyb: syntaktické, běhové a logické (sémantické). Syntaktické chyby objeví překladač při překladu, běhové způsobí zhavarování programu za běhu, logické se projeví nesprávným výsledkem, který však nemusíme na první pohled poznat. Objeví-li překladač při překladu nějakou chybu, oznámí nám to ve spodním, informačním poli okna editoru. Není-li nám význam chyby zřejmý, můžeme požádat o nápovědu stiskem tlačítka s otazníkem na pravém kraji informačního pole. Pro efektivní vývoj programů je výhodné napsat nejprve testy a teprve pak testovaný program. Celý vývoj pak směřuje k tomu, abychom tyto testy „rozchodili“. Tuto metodiku nazýváme vývoj programů řízený testy. BlueJ umožňuje poloautomatickou tvorbu testů, při níž sleduje naše počínání a na požádání je uloží jako program. Při testech využíváme testovací přípravek (test fixture), který připraví před každým testem předem definované počáteční podmínky a po testu zase v případě potřeby vše „uklidí“. Definice konstruktoru sestává z hlavičky a těla. Hlavička obsahuje modifikátory následované názvem konstruktoru, který je shodný s názvem třídy, a seznamem parametrů uzavřeným v kulatých závorkách. Nemá-li konstruktor žádné parametry, budou závorky prázdné. Má-li být konstruktor viditelný zvenku třídy, označíme jej modifikátorem public. Parametr deklarujeme tak, že uvedeme jeho typ (u objektových parametrů uvedeme jako jejich typ třídu, na jejíž instanci odkazují) následovaný názvem (identifikátorem) parametru, prostřednictvím nějž se k němu budeme v těle konstruktoru obracet. Je-li pro nás výhodné zavolat v konstruktoru jiný konstruktor téže třídy, zavoláme jej tak, že napíšeme klíčové slovo this následované seznamem parametrů. Překladač pozná, který konstruktor chceme zavolat, podle počtu a typu parametrů. Volání konstruktoru this musí být úplně prvním příkazem těla konstruktoru, před ním smějí být již pouze mezery a komentáře. Pro přesný zápis pravidel, podle nichž se vytváří (zapisuje) vysvětlovaná konstrukce, používáme syntaktické definice. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 172 z 433 Kapitola 3: Vytváříme vlastní třídu 173 Zaslání zprávy objektu realizujeme zavoláním odpovídající metody. Metody se musí navzájem lišit svým jménem (identifikátorem) a/nebo seznamem typů parametrů. Metody (včetně konstruktorů), které se liší pouze počtem a/nebo typem parametrů označujeme za přetížené. Definice metody sestává z hlavičky a těla. Hlavička obsahuje modifikátory následované typem návratové hodnoty, názvem metody a seznamem parametrů uzavřeným v kulatých závorkách. Metody, které nic nevrací, mají jako typ návratové hodnoty uveden typ void. Metody vracejí požadované hodnoty tak, že jako poslední vykonávaný příkaz uvedou příkaz return následovaný výrazem, jehož hodnotu metoda vrací. Jednotlivé verze přetížených metod budeme rozlišovat uvedením seznamu typů parametrů v závorkách za názvem metody. Atributy deklarujeme v těle třídy, avšak mimo těla jejich metod. Deklarace atributů sestává ze seznamu modifikátorů následovaného typem definovaného atributu a jeho identifikátorem (názvem) a případným přiřazením počáteční hodnoty. Atributy s výjimkou konstant, které není možné změnit, označujeme modifikátorem private. U tříd rozeznáváme jejich rozhraní, tj. to, co o sobě třída zveřejní a na co se mohou její uživatelé spolehnout, a implementaci, tj. to, jak třída zařídí, že umí to, co vyhlásila v rozhraní. Implementační detaily bychom měli před okolím skrývat, aby nebylo možno funkčnost třídy a jejích instancí ohrozit. Vedle explicitně deklarované části rozhraní, tj. hlaviček veřejných metod, je součástí rozhraní i tzv. kontrakt popisující rysy, které není možno přímo specifikovat prostředky jazyka. Použité metody a atributy musíme vždy kvalifikovat, tj. napsat před ně název odkazu na instanci (u atributů a metod třídy můžeme použít název třídy), o jejíž metodu nebo atribut se jedná. Kvalifikaci můžeme vynechat pouze v případě, kdy se na danou metodu či atribut obracíme v metodě instance (třídy), na jejíž metodu či atribut se obracíme. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 173 z 433 174 Myslíme objektově v jazyku Java 1.5 Chceme-li zdůraznit, že se obracíme na atribut či metodu té instance, jejíž metodu právě definujeme, kvalifikujeme ji klíčovým slovem this. V případě potřeby můžeme uvnitř metody deklarovat lokální proměnné. Deklarace musí obsahovat typ proměnné (u objektových typů třídu instance, na níž bude proměnná odkazovat), identifikátor (název) a případně i přiřazení počáteční hodnoty. Lokální proměnnou nelze použít, dokud se jí nepřiřadí nějaká hodnota. Po ukončení metody jsou všechny její lokální proměnné ztraceny. Potřebuje-li si metoda něco pamatovat mezi svými spuštěními, musí si to uložit do nějakého atributu. Atributy a metody třídy definujeme tak, že mezi jejich modifikátory uvedeme klíčové slovo static. Atributy a metody třídy bývají často označovány jako statické. Má-li být hodnota atributu neměnná (konstantní), uvedeme mezi jeho modifikátory klíčové slovo final. Statickým konstantám je třeba přiřadit jejich hodnotu již v deklaraci, nestatickým konstantám je možno přiřadit počáteční hodnotu v konstruktoru. Jako veřejné by měly být z doposud probraných typů deklarovány pouze konstanty číselných typů, logické konstanty a konstanty typu String. Metody mohou mít i parametry objektových typů. Při zadávání takovýchto parametrů v prostředí BlueJ můžeme využít možnosti zadat takovýto parametr klepnutím na příslušný odkaz v zásobníku odkazů. Jako literály označujeme hodnoty přímo zapsané do programu, tj. přímo zadaná čísla, textové řetězce a konstanty true, false a null. Prázdný řetězce "" a prázdný odkaz na řetězec (null) jsou různé věci a je potřeba je rozlišovat. Prázdný řetězec je exitující řetězec, který neobsahuje žádný znak. Naproti tomu prázdný odkaz je odkaz, který nikam neukazuje. Jazyk Java používá dva druhy komentářů: obecný, který je ohraničen komentářovými závorkami /* a */, a řádkový, který začíná znaky // a končí spolu s koncem řádku. Komentář můžeme napsat kdekoliv, kde můžeme napsat mezeru. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 174 z 433 Kapitola 3: Vytváříme vlastní třídu 175 Obecný komentář začínající znaky /** je chápán jako dokumentační. Zapisují se do něj informace užitečné pro budoucí uživatele dané třídy, metody, konstanty atd. Vývojové prostředí Javy obsahuje program, který projde zdrojový kód a z dokumentačních komentářů vytvoří standardní dokumentaci. V okně editoru můžeme zadat, zda má editor zobrazit implementaci dané třídy (zdrojový kód) nebo její rozhraní (dokumentaci). Při žádosti o zobrazení dokumentace BlueJ vyvolá program, který dokumentaci automaticky vytvoří. Dokumentační komentáře musíme napsat těsně před dokumentovanou konstrukci (třídu, atribut, metodu). Dokumentační komentáře mohou vedle prostého textu obsahovat i HTML značky (tag). Java zavádí několik speciálních značek začínajících znakem „@“, které slouží k lepšímu popisu některých rysů dokumentovaných konstrukcí, např. parametrů či návratových hodnot dokumentovaných metod. Zadáním příkazu Nástroje → Dokumentace projektu v okně projektu požádáme o vygenerování dokumentace všech tříd v projektu. Tuto dokumentaci si pak můžeme prohlížet HTML prohlížečem nezávisle na spuštění BlueJ. Má-li být aplikace spustitelná i mimo BlueJ, musí obsahovat třídu s veřejnou statickou metodou main s parametrem typu String[]. Takovouto třídu můžeme jednoduše vytvořit přímo v BlueJ stiskem tlačítka Nová třída a následným nastavením přepínače Typ třídy na Třída spouštějící aplikaci. V takto vytvořené třídě pak stačí zapsat do metody main kód pro spuštění aplikace. Vytvořené aplikace se pakují do souborů s příponou jar, které je pak možno spouštět obdobně jako soubory exe. Aplikaci zapakujeme zadáním příkazu Projekt → Export a nastavení příslušných voleb. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 175 z 433 176 4. Myslíme objektově v jazyku Java 1.5 Dotváříme vlastní třídu Kapitola 4 Dotváříme vlastní třídu ☯ Co se v kapitole naučíme V této kapitole náš projekt ještě neopustíme. Ve třetí kapitole jsme se naučili naprogramovat konstrukce, s nimiž jsme se seznámili ve druhé kapitole. Nyní rozšíříme svůj repertoár o dovednosti, s jejichž protějšky jsme se sice při našich hrátkách v druhé kapitole nesetkali, ale které nicméně patří do základního rejstříku dovedností objektově orientovaného programátora. V této kapitole vystačíme zpočátku ještě s projektem z minulé kapitoly, který budeme v případě potřeby doplňovat třídami, jež převezmeme z projektu 03_Třídy_Z. 4.1 Jednoduché vstupy a výstupy V minulé kapitole jsme si pověděli o literálech, avšak jejich použití jsme si moc nevyzkoušeli. Hned to napravíme. Při té příležitosti si zároveň ukážeme, jak můžete jednoduše uživateli něco sdělit nebo se jej na něco zeptat. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 176 z 433 Kapitola 4: Dotváříme vlastní třídu 177 Doplnění projektu o třídu odjinud Metody, o kterých budu za chvíli hovořit, jsou ve třídě VstupVýstup, která je součástí projektu 03_Třídy_Z. Než s nimi začneme pracovat, ukážu vám, jak můžete doplnit svůj projekt třídou z jiného projektu nebo jakéhokoliv jiného pro vás dosažitelného místa (z disku, sítě, …). 1. V okně projektu zadejte příkaz Úpravy → Nová třída ze souboru…. 2. BlueJ otevře dialogové okno pro zadání souboru. Najděte požadovanou třídu, klepněte na ni a stiskněte Přidat. 3. BlueJ přidá zadanou třídu do vašeho projektu. Jak prosté! Textové řetězce Když jsem vás v podkapitole Datové typy na straně 52 poprvé seznamoval s datovým typem String, říkal jsem vám, že je to nejpoužívanější objektový datový typ. Protože textové řetězce budeme používat i při práci s ostatními typy literálů, tak s nimi náš výklad začneme. Každý objekt je převeditelný na řetězec. Má k tomu vyhrazenu speciální metodu toString(). Která třída nemá definovanou vlastní podobu této metody, té dodá potřebnou metodu třída Object. V pasáži o textových literálech jsem vám ukazoval, jak je možno s využitím operátoru + vytvořit dlouhý řetězec z několika řetězců menších. Tyto menší řetězce však nemusí být textové literály, mohou to být jakékoliv řetězce. Java jde dokonce tak daleko, že vám umožní přidat k řetězci cokoliv. Zjistí-li, že k řetězci „přičítáte“ něco jiného než řetězec, tak to prostě na řetězec převede. Řeknu to trochu přesněji: Bude-li v součtu kterýkoliv ze sčítanců řetězec, převede se druhý sčítanec také na řetězec a výsledkem takovéhoto řetězcového součtu bude řetězec vzniklý spojením sčítanců. ☺ Uvedeného pravidla se často využívá k převodu hodnot nejrůznějších typů na řetězec. Máte-li např. metodu, která vyžaduje jako svůj parametr řetězec, a chcete jí předat hodnotu jiného typu, stačí ji předat výraz ""+hodnota. Překladač najde součet prázdného řetězce s hodnotou a podle předchozího pravidla nejprve převede hodnotu na řetězec a pak oba řetězce sečte. Protože ale přičtením prázdného řetězce nic nezměníme, bude výsledkem hodnota převedená na textový řetězec. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 177 z 433 178 Myslíme objektově v jazyku Java 1.5 Zkusíme si malý experiment. Otevřete importovanou třídu VstupVýstup a najděte v ní následující statickou metodu: 1 public class VstupVýstup 2 { 3 public static String jméno() { 4 5 String křestní = P.zadej( "Zadej své křestní jméno:", "Pepa" ); 6 String příjmení = P.zadej( "Zadej své příjmení:", "" ); 7 String jméno = křestní + " " + příjmení; 8 P.zpráva( "Jmenuješ se:\n\n" + jméno ); 9 return jméno; 10 } 11 } Projděme si nyní tuto metodu příkaz za příkazem. Na pátém řádku je zavolána statická metoda třídy P, která otevře dialogové okno, v němž zadáte požadovanou hodnotu. První parametr této metody je výzva, kterou vám program oznamuje, co vlastně máte zadat. Druhým parametrem je pak počáteční hodnota, kterou vám program ve vstupním poli nabídne. Po provedení tohoto příkazu se tedy na obrazovce objeví dialogové okno z obr. 4.1. Obrázek 4.1 Dialogové okno vyvolané příkazem z pátého řádku Po zadání požadované hodnoty (tj. vašeho křestního jména) metoda vrátí zadaný řetězec a program jej uloží do proměnné křestní (pořád jsme na řádku 5). Na dalším řádku vás program požádá o zadání vašeho příjmení. Oproti předchozímu příkazu se však již nepokouší o žádný odhad toho, jak byste se mohli jmenovat, a jako počáteční hodnotu ve vstupním poli proto zadává prázdný řetězec. Zadanou hodnotu od metody zadej převezme a uloží ji do proměnné příjmení. Na sedmém řádku program složí obě části vašeho jména dohromady. Všimněte si, že mezi ně bystře vkládá mezeru, protože jinak by na sebe byly nalepené (můžete si to vyzkoušet). Výsledný textový řetězec uloží do proměnné jméno. Osmý řádek vám v dialogovém okně prozradí, jaké jméno jste právě zadali. Pokud bych tedy správně zadal svoje jméno, otevřelo by se okno z obr. 4.2. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 178 z 433 Kapitola 4: Dotváříme vlastní třídu 179 Obrázek 4.2 Počítač mi prozradil, co jsem zadal Poslední řádek metody jméno vrací vytvořený řetězec tomu, kdo metodu zavolal. Protože jsem metodu zavolal přímo z prostředí BlueJ, vrátí metoda hodnotu volajícímu prostředí. Jak víme, BlueJ po zavolání metod, jež něco vracejí, otevře dialogové okno, v němž nám prozradí, co že jim volaná metoda vrátila (viz obr. 4.3). Obrázek 4.3 BlueJ vám prozradí, co mu metoda vrátila Rozdíl mezi prázdným řetězcem a null V souvislosti s textovými řetězci bych vás chtěl upozornit ještě na jednu drobnost, která dělává začátečníkům potíže. Jde o to, že si občas neuvědomují, že prázdný řetězec a žádný řetězec jsou dvě různé věci. Prázdný řetězec, tj. řetězec "", je řetězec, který neobsahuje žádný znak. Pořád je to ale platný řetězec, jenom v něm nic není. Uložíme-li proto do proměnné odkaz na prázdný řetězec, tak proměnná opravdu někam odkazuje. Odkazuje na něco, co existuje. Naproti tomu vložíme-li do řetězcové proměnné null, říkáme tím, že proměnná v danou chvíli nikam neukazuje, takže nemá smysl se o dokazované instanci bavit a už vůbec nemá smysl se jí pokoušet použít. A teď pozor, přijde myšlenkový přemet: I když nemá smysl se bavit o instanci, na níž taková proměnná odkazuje (když přece nikam neodkazuje), má smysl se bavit o tom, kam proměnná odkazuje – přece nikam. To je informace, kterou často potřebujeme vědět – např. proto, abychom věděli, můžeme-li proměnnou někde použít, nebo musíme-li jí nejprve přiřadit odkaz na nějakou existující instanci. Rozdíl můžete zjistit i při spuštění metody jméno, kterou jsme před chvílí naprogramovali. Nezadáte-li nic a stisknete OK, vrátí metoda zadej odkaz na prázd@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 179 z 433 180 Myslíme objektově v jazyku Java 1.5 ný řetězec (zadali jste řetězec, v němž není žádný znak). Stisknete-li však Cancel nebo zavřete-li okno tlačítkem na titulkové liště či stiskem ESC, vrátí prázdný ukazatel (null), protože jste nic nezadali (na obsahu textového pole v okamžiku zavření okna v takovém případě nezáleží). Podle toho vypadá i text, který vám metoda vypíše v následující zprávě. Čísla Tak jsme si pohráli s textovými řetězci a můžeme si jít hrát s čísly. Zkuste do třídy VstupVýstup přidat takovou malou děličku. Rozdíl oproti minulému programu bude v tom, že uživatel nebude zadávat jména, ale čísla, která je třeba vydělit, a ve výsledném okně se pak dozví výsledek. (Vzorové řešení za chvilku uvedu.) Protože se ale výsledek dělení liší v závislosti na typu svých argumentů, naprogramujte dělící metody hned dvě: první nazvěte celočíselnéDělení a druhou reálnéDělení. Využijte přitom toho, že metoda zadej má tři přetížené verze, které se liší typem zadávané implicitní hodnoty. Zadáte-li jako implicitní hodnotu celé číslo, vrátí vám také celé číslo (zadá-li uživatel nějaký nesmysl, který není možno interpretovat jako celé číslo, tak se vzbouří). Zdáte-li jako implicitní hodnotu číslo typu double, vrátí vám zadanou hodnotou jako hodnotu typu double, a to i tehdy, když uživatel zadal celé číslo. Zkuste v obou metodách zjistit nejenom podíl, ale také zbytek po dělení. Ten sice nebudete moci vrátit jako funkční hodnotu, protože Java umožňuje vrátit pouze jednu věc, ale můžete jej alespoň vytisknout v okně s výsledkem dělení. Protože jste už velice podobnou metodu viděli, mohli byste se o její naprogramování pokusit sami. Až budete se svým dílem hotovi, vyzkoušejte ji a pak se podívejte na následující program a porovnejte, co jste dělali jinak. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public static int celočíselnéDělení() { int dělenec = P.zadej( "Zadej dělence (celé číslo):", 0 ); int dělitel = P.zadej( "Zadej dělitele (celé číslo):", 1 ); int podíl = dělenec / dělitel; int zbytek = dělenec % dělitel; P.zpráva( dělenec + " : " + dělitel + " = " + podíl + ", zbyde " + zbytek ); return podíl; } public static double { double dělenec = double dělitel = double podíl = double zbytek = reálnéDělení() P.zadej( "Zadej dělence:", 0 ); P.zadej( "Zadej dělitele:", 1 ); dělenec / dělitel; dělenec % dělitel; @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 180 z 433 Kapitola 4: Dotváříme vlastní třídu 19 20 21 22 } 181 P.zpráva( dělenec + " : " + dělitel + " = " + podíl + "\n\"zbytek\" = " + zbytek ); return podíl; Jak vám předchozí program prozradil, operátor získání zbytku po dělení je definován i pro reálná čísla. Nebudu vám tu ale vysvětlovat, jako pracuje. Zkuste s ním zaexperimentovat a přijděte na to sami. 4.2 Knihovny statických metod V některých případech vytváříme třídu ne jako „továrnu na instance“, ale jako úložiště často používaných metod. Jsou to vlastně takové knihovny. Typickým příkladem byla třída VstupVýstup, se kterou jsme si hráli před chvílí nebo třída P, s níž pracujeme již od počátku třetí kapitoly. Metody v těchto třídách bývají definovány výhradně jako statické a vše, co potřebují, jim musíme předat v jejich parametrech. Pouze ve výjimečných případech uchovávají některé sdílené informace v atributech třídy – např. hodnoty klíčových konstant (třeba číslo π). Když víme, že instanci takovéto třídy nebudeme potřebovat (její metody si vystačí samy), bylo by vhodné zařídit, aby tato instance ani vytvořit nešla. Toho dosáhneme jednoduše: definujeme soukromý bezparametrický konstruktor. (Teoreticky bychom mohli definovat i parametrický, ale žádného zlepšení bychom tím nedosáhli a jenom bychom to zbytečně zkomplikovali.) Definicí soukromého konstruktoru zabezpečíme, že nikdo nebude moci vyrobit instanci této třídy. Protože je konstruktor soukromý, nikdo k němu nemůže, a protože je konstruktor již definován, nestane se, že by překladač za nás vyrobil konstruktor implicitní. Definice soukromého bezparametrického konstruktoru zamezujícího tvorbu instancí je proto použita i ve třídě VstupVýstup. 4.3 Podrobnosti o operátorech Na chvilku si odpočineme od našeho stromu a rozšiřování jeho vlastností a povíme si něco o možnostech, které nám Java nabízí pro počítání. Doposud jsme se setkali s devíti operátory (ani jste si nevšimli, co?). Zopakujeme si nyní jejich @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 181 z 433 182 Myslíme objektově v jazyku Java 1.5 význam a doplňme si naše znalosti o některé jejich další vlastnosti. Pak se seznámíme s novou skupinou operátorů. Než se rozpovídám o operátorech, připomenu vám nejprve několik termínů, které budu vzápětí používat: Operace je to, co se provede. Operace sčítání sečte dvě čísla, operace přiřazení přiřadí proměnné nějakou hodnotu, operace vložení do závorek zařídí, že se uzavřený výraz nejprve spočte a teprve pak použije, atd. Operátor je znak nebo skupina znaků, které oznamují, jaká operace se má v daném místě provést. Chceme-li dvě čísla sečíst, vložíme mezi ně operátor +, chceme-li zařídit, aby se část výrazu spočetla přednostně, vložíme před ní a za ní odpovídající kulaté závorky. Jak ukazují právě závorky, mezi operátory existují i takové, které jsou tvořeny několika znaky, z nichž každý stojí ve výrazu někde jinde. Operand je výraz, s nímž se provádí operace. Sčítáme-li dvě hodnoty, označujeme je jako operandy. Uzavíráme-li část výrazu do závorek, je tato část operandem závorek. Arita Tak tohle slovo asi většina z vás ještě neslyšela. Arita operace říká, kolik v ní vystupuje operandů. Rozeznáváme operace unární (s jedním operandem – např. závorky), binární (se dvěma operandy – např. sčítání) a ternární (se třemi operandy – taková je v Javě jenom jedna a setkáme se s ní, až budeme učit počítač přemýšlet). Binární aritmetické operátory + – * / % Sčítání, odčítání, násobení První tři z operátorů uvedených v nadpisu, tj. operátor sčítání (+), odčítání (–) a násobení (*), jsou průzračné a nedělají nikomu potíže. Každý z nich spočte hodnoty výrazů vpravo a vlevo od operátoru a s výsledky provede požadovanou operaci. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 182 z 433 Kapitola 4: Dotváříme vlastní třídu 183 Jediné, na co nesmíte při jejich používání zapomenout je, že v programování má, stejně jako v matematice, násobení přednost před sčítáním. Potřebujete-li proto nejprve dvě hodnoty sečíst a teprve pak vynásobit, máte dvě možnosti: První možnost znáte z matematiky: sčítané hodnoty uzavřete do závorek a výsledný součet vynásobíte – např. (3 + 4) * (5 + 6). Druhá možnost je trochu víc programátorská – celou složitou operaci prostě rozdělíte do několika kroků: int a = 3 + 4; int b = 5 + 6; int c = a * b; Většinou dáte přednost závorkám, nicméně existují situace, kdy je výhodnější celý výpočet rozdělit do několika kroků. Možná to některé překvapí, ale výsledný program je v obou případech prakticky stejný. Podíváte-li se totiž do přeloženého programu, zjistíte, že nerozdělíte-li výpočet do několika kroků vy, udělá to za vás překladač. Dávejte proto vždy přednost takovému zápisu, který je pro vás přehlednější. Slučování řetězců + Jak již víme, operátor + je možno použít nejenom jako operátor sčítání v aritmetických výrazech, ale také jako operátor slučování řetězců. Řekli jsme si, že jakmile je některý z operandů operátoru + řetězec, převede se na řetězec i druhý operand a výsledkem je řetězec vzniklý jejich spojením. Někdy ale potřebujeme ve slučovaném řetězci napřed něco sečíst – pak musíme tento součet uzavřít do závorek, aby se nejprve sečetl a teprve výsledek převedl na řetězec a použil při slučování. Budeme-li tedy chtít zobrazit výsledek násobení čísel a a b, budeme zobrazovaný řetězec generovat např. následovně: String násobilka = a + " * " + b + " = " + (a*b); Dělení / Možná se divíte, proč uvádím dělení zvlášť, vždyť je to stejná operace jako předchozí tři. Je a není. Na dělení je zajímavé to, že výsledek závisí na tom, jakého typu jsou operandy. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 183 z 433 184 Myslíme objektově v jazyku Java 1.5 Je-li některý z operandů reálné číslo (např. typu double), je výsledkem opět reálné číslo, a to „přesný“ podíl – např. 2.0/4 je 0.5 (připomínám, že v programování se místo desetinné čárky píše desetinná tečka). Jsou-li oba operandy celočíselné, je výsledkem celočíselný podíl, tj. to, co zbude z podílu po odříznutí jeho desetinné části. Zůstaneme-li u příkladu z předchozího odstavce, pak platí, že 2/4 je 0 (nula), 12/5 je 2 a -12/5 je -2. Chcete-li si vyzkoušet, jak celočíselné dělení pracuje, zkopírujte z projektu 03_Třídy_Z třídu Počty a spusťte její statickou metodu dělení(). Výstupní okno ukazuje obr. 4.4. Obrázek 4.4 Výsledky dělení Metoda dělení() má „zadrátovaný“ jediný příklad (pak se dá jednoduše dosáhnout pěkného zarovnání pod sebou). Vedle toho existuje v nabídce i metoda dělení(double,double), která vám umožní zadat dvě čísla, jejichž podíly budou zobrazeny v dialogovém okně (její výsledky už tak pěkně zarovnané být nemusí). Zkuste si s ní trochu zaexperimentovat. Zbytek po dělení (dělení modulo) % Dělení modulo vrátí zbytek po dělení výrazu vlevo výrazem vpravo. Výsledky se tentokrát počítají pro reálná i celá čísla stejně: odčítáte dělitele od dělence (jmenovatele od čitatele) tak dlouho, až vám zbude tak malé číslo, že byste při příštím odčítání ʺpřeběhli přes nuluʺ (takto je operace popsána v definici jazyka). Z toho zákonitě plyne, že znaménko výsledku respektuje znaménko dělence. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 184 z 433 Kapitola 4: Dotváříme vlastní třídu 185 Obrázek 4.5 Výsledky dělení modulo I s dělením modulo můžete zaexperimentovat. Pro tento účel je ve třídě Počty připravena metoda modulo(double,double), které zadáte dělence a dělitele a ona vám vrátí sadu výsledků operace. Unární operátory + – Operátory + a – mohou vystupovat v několika rolích. U operátoru + jsme si již ukazovali, že podle typu operandů je to jednou operátor sčítání a podruhé operátor slučování řetězců. Nyní si ukážeme jeho třetí podobu. Oba operátory mohou pracovat také jako unární operátory, které pouze ovlivňují znaménko svého (jediného) operandu. Ve škole vás učili, že je jedno, napíšeme-li 5 nebo +5 a učili vás také o významu osamoceného znaménka – před číslem nebo před výrazem – např. -5 nebo –(a + b). Java v tomto směru nepřináší nic nového. Jediné, na co musíte dávat pozor, je to, abyste náhodou nenapsali dva stejné operátory těsně vedle sebe (tj. bez vložené mezery), protože to by překladač mohl pochopit jako něco úplně jiného (blíže se s touto možností seznámíme v kapitole Inkrementační a dekrementační operátory na straně 191). Kulaté závorky ( ) Jak jsem již řekl, závorky jsou v programování chápány také jako operátor, a to jako operátor, který má přednost před všemi ostatními. Jako každý operátor i závorky vracejí hodnotu, a to hodnotu výrazu, který je v nich uzavřen. Použití závorek snad nemusím vysvětlovat. Je stejné jako v matematice. Jediným rozdílem je to, že v matematice zpřehledňujeme výrazy používáním několika různých druhů závorek, kdežto v programování používáme k označení toho, co se má spočítat jako první, vždy pouze kulaté závorky. Ostatní druhy závorek slouží k jiným účelům. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 185 z 433 186 Myslíme objektově v jazyku Java 1.5 V programovacích jazycích se používá velké množství operátorů. Každý z nich má definovanou tzv. prioritu, podle které se pozná, kdo má před kým přednost (víte například, že násobení má přednost před sčítáním). Jen málo programátorů si dokáže pamatovat celou posloupnost priorit. Nebudu k tomu nutit ani vás, protože vždy existuje jednoduchá pomoc: nevíte-li, co se bude dělat dřív, použijte závorky. Někteří programátoři se za používání závorek asi stydí, protože je dávají opravdu pouze tam, kde to bez nich nejde. Myslete ale na to, že je lepší mít program se závorkami, ale bez chyb, než naopak. Kromě toho myslete i na nešťastníky, kteří budou číst program po vás a nebudou mít třeba tak dokonalé znalosti toho, kdo má před kým přednost, a mohli by proto váš program pochopit špatně. Přiřazovací operátor = Operátor přiřazení spočte hodnotu výrazu, který je vpravo od něj a uloží ji do proměnné, která je vlevo od něj – např. ve výrazu počet = počet + 1 se nejprve spočte, kolik je počet+1 a výsledek se pak uloží jako nová hodnota do proměnné počet (výsledkem je tedy zvýšení hodnoty proměnné počet o jedničku). Operace přiřazení však nepředstavuje pouze vlastní akci spočtení a uložení nové hodnoty. I ona, stejně jako např. aritmetické operace, vrací hodnotu. Touto vracenou hodnotou je přiřazovaná hodnota. Na vyzkoušení jsem vám opět připravil jednoduchou metodu, která testuje pravdivost tvrzení, že (a + b) * (a - b) = a*a – b*b. 1 public static void přiřazení( int a, int b ) 2 { int a2, b2, součet, rozdíl, součin; 3 String s1 = "Čísla: a=" + a + ", b=" + b; 4 String s2 = "\nMocniny: a2=" + (a2=a*a) + ", b2=" + (b2=b*b); 5 6 String s3 = "\nsoučet=" + (součet=a+b) + ", rozdíl=" + (rozdíl=a-b); 7 String s4 = "\nRozdíl mocnin (a2 - b2): " + (a2 - b2); 8 String s5 = "\nSoučin (a+b)*(a-b): " + (součet*rozdíl); P.zpráva( s1 + s2 + s3 + s4 + s5 ); 9 10 } Používat ve výrazech hodnoty vrácené operací přiřazení tak, jak to dělá metoda přiřazení(int,int) se moc nedoporučuje, protože to většinou znepřehledňuje program. Jsou ale situace, kdy takovou možnost přivítáte. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 186 z 433 Kapitola 4: Dotváříme vlastní třídu 187 Sdružené přiřazovací operátory +=, –=, *=, /=, %= Java umožňuje sloučit kteroukoliv z aritmetických operací s operátorem přiřazení a zrychlit tak celý výpočet. Princip je jednoduchý – budu-li místo příslušného aritmetického operátoru + - * / % psát znak #, pak výraz operand_1 #= operand_2; bude naprosto ekvivalentní výrazu operand_1 = operand_1 # (operand_2); Všimněte si, že v posledním výrazu je operand_2 uzavřen do závorek. Je to proto, že operandem může být i výraz a operátor použitý ve výrazu by se nám mohl „pohádat s operátorem + o to, kdo bude mít přednost. Vezměte si např. výraz a *= b + c; Pokud bychom nechtěli použít složený přiřazovací operátor, museli bychom tento výraz přepsat do tvaru: a = a * (b + c); protože kdybychom nepoužili závorky, tak by se napřed vynásobilo a*b a teprve pak by se přečetlo c. Složený přiřazovací příkaz za vás takovéto dilema jednoduše vyřeší. Aby se vám sdružené operátory dostaly alespoň trochu pod kůži (v programech totiž na ně narazíte velmi často), připravil jsem jednoduchou metodu sdružené(int,int), s jejíž pomocí si můžete zase vše vyzkoušet: 1 public static void sdružené ( int a, int 2 { String s0 = "Start: a=" + a + ", 3 4 a += a + b; b += b; 5 6 String s1 = "\nPrvní: a=" + a + ", a += a + b; 7 8 b += b; String s2 = "\nDruhá: a=" + a + ", 9 a += a + b; 10 11 b += b; 12 String s3 = "\nTřetí: a=" + a + ", 13 P.zpráva( s0 + s1 + s2 + s3 ); 14 } b ) b=" + b; b=" + b; b=" + b; b=" + b; Zkoušejte volat metodu sdružené(int,int) s různými hodnotami parametrů a vyzkoušejte si i změnu sdruženého operátoru – použijte např. místo operátoru += operátor *=. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 187 z 433 188 Myslíme objektově v jazyku Java 1.5 Operátor přetypován (typ) Jsou okamžiky, kdy bychom potřebovali změnit typ nějakého výrazu. Někdy je to proto, že nám aktuální typ výrazu nevyhovuje a změna jeho typu by pomohla, jindy je to proto, že víme něco, co překladač neví, a můžeme mu tímto způsobem napovědět (s takovouto situací se setkáme v 6. kapitole). V pasáži o operátoru dělení jsme si například říkali, že výsledek dělení závisí na typech operandů. Jsou-li oba celočíselné, je výsledkem celá část podílu, je-li alespoň jeden z nich reálný, je výsledkem reálné číslo reprezentující přesný podíl. Nyní si představte, že máme celá čísla a a b a potřebujeme spočítat přesný podíl výrazu (a-b)/(a+b). Protože součet i rozdíl celých čísel je celé číslo, budeme dělit dvě celá čísla a budou-li obě čísla kladná, bude výsledek vždy nula, protože čitatel bude vždy menší než jmenovatel. Budeme-li požadovat přesný podíl, nemůžeme čísla jen tak podělit, ale musíme si nějak pomoci. Máme v podstatě dvě možnosti: Součet a/nebo rozdíl uložíme do reálné proměnné, takže v podílu bude alespoň jeden operátor reálný a získáme tak reálný i výsledek – např. public double podíl( int a, int b ) { double součet = a + b; double rozdíl = a – b; return rozdíl / součet; } Jeden z operandů přetypujeme (můžeme klidně i oba) na reálné číslo, takže budeme dělit např. reálné číslo celým. Jak víme, v takovém případě překladač převede na reálné číslo i druhý operand a vrátí požadovaný „přesný podíl“ – např.: public double podíl( int a, int b ) { return (double)(a - b) / (a + b); } Jak jste mohli vidět v předchozí ukázce, k přetypování se používá název cílového typu uzavřený v kulatých závorkách. To celé, tj. závorka-typ-závorka, pak označujeme jako operátor přetypování. Před každým takovýmto přetypováním si počítač nejprve zkontroluje, jestli je povolené. Pokud bychom např. chtěli přetypovat logickou hodnotu na číslo, se zlou bychom se potázali. Obdobně bychom nepochodili ani v případě, že bychom chtěli na číslo přetypovat instanci nějakého objektového typu. Z typů, které jsme probrali, můžeme prozatím navzájem přetypovávat pouze číselné typy a znakový typ: @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 188 z 433 Kapitola 4: Dotváříme vlastní třídu 189 Přetypováním celého čísla na reálné získáme reálné číslo s hodnotou přetypovávaného celého čísla. Přetypováním reálného čísla na celé dostaneme číslo s useknutou desetinnou částí. Přetypováním znaku na číslo získáme kód daného znaku. Přetypováním čísla na znak získáme znak s daným kódem. Překladač si vždy zkontroluje, jestli je příslušné přetypování povoleno. Budete-li chtít např. přetypovat logickou hodnotu nebo instanci objektového typu na celé číslo, překladač vám to nepovolí. Pseudopřetypování na String Kromě výše uvedených „řádných“ přetypování bych tu přidal ještě jedno „falešné“. Je jím přetypování čehokoliv na řetězce (String). „Řádné“ přetypování na řetězce pomocí operátoru (String) vám totiž překladač většinou nepovolí. Co ale projde vždy, je výraz ""+x, kde x může být proměnná libovolného typu, objektového i primitivního (již jsem se o tom zmiňoval v poznámce na straně 177). Důvod je jednoduchý – nejedná se o přetypování, ale o sloučení řetězců. Protože na levé straně operátoru + stojí řetězce, převede počítač i pravý operand na řetězec (u instancí objektových typů zavolá jejich metodu toString() a dosadí vrácený výsledek) a k výsledku přidá prázdný řetězec, tj. nic. Místo původního x (přitom x nemusí být jen proměnná, ale může to být i výraz, který je nejdříve potřeba spočítat) tak získáme řetězec, který je tou nejlepší řetězcovou reprezentací x. Uvedený postup je sice univerzální a vždy funguje, ale není nejrychlejší, protože se při něm dělá několik zbytečných operací. Chcete-li ušetřit nějakou tu nanosekundu, musíte u objektových typů zavolat přímo metodu toString() a u primitivních typů statickou metodu valueOf jejich obalového typu, což je objektový typ, který zastupuje daný primitivní typ v situacích, ve kterých se primitivní typy používat nemohou (podrobněji si o nich povíme v kapitole Kontejnery nejsou jen na odpadky na straně 402). Prozatím vám prozradím, že pro převod celých čísel slouží metoda Integer.valueOf(int), reálných čísel slouží metoda Double.valueOf(int), logických hodnot slouží metoda Boolean.valueOf(int). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 189 z 433 190 4.4 Myslíme objektově v jazyku Java 1.5 Počítáme instance Zanechme nyní na chvíli teorie a pojďme se opět podívat, jak bychom mohli dále zdokonalit naši geniální třídu kreslící strom. V řadě situací bývá výhodné počítat vytvořené instance a přidělit každé vytvořené instanci její „rodné číslo“, do kterého uložíme pořadí vytvořené instance. Toto rodné číslo bude pro danou instanci konstantní. Dostane je „při narození“ a udrží si je až do smrti. Bude ji odlišovat od všech ostatních instancí dané třídy. Podívejme se, jak bychom takovouto úlohu vyřešili pro náš strom. Nejprve musíme vymyslet, jak poznáme, kolikátou instanci vytváříme. K tomu může sloužit atribut třídy, který bude mít na počátku nulovou hodnotu a při vytváření nové instance k němu vždy připočteme jedničku. Nazvěme tento atribut počet, protože v něm bude neustále uchováván počet vytvořených instancí dané třídy. Rodné číslo instance můžeme uchovávat v jiném atributu, do kterého při vytváření instance uložíme aktuální počet vytvořených instancí, který bude současně pořadovým číslem vzniku dané instance. Protože tento atribut označuje pořadí vzniku dané instance, nezveme jej pořadí, a protože se jeho hodnota nebude v průběhu života instance měnit, definujme jej jako konstantu. Abychom mohli toto rodné číslo co nejlépe využít, definujeme ještě konstantní „stringový“ atribut název, jehož obsahem bude názve třídy následovaný podtržítkem a rodným číslem. Tento atribut doplníme přístupovou metodou getNázev(), která nám název instance na požádání vrátí. Část těla třídy Strom, která tuto úlohu řeší, by mohla být naprogramovaná např. následovně: 1 public class Strom 2 { 3 // ... 4 /** Počet doposud vytvořených instancí. */ 5 private static int počet = 0; 6 7 /** Pořadí kolikátá byla daná instance vytvořena v rámci třídy. */ 8 private final int pořadí; 9 10 11 /** Název sestávající z názvu třídy a pořadí instance */ private final String název = "Strom_" + pořadí; 12 13 14 /*************************************************************************** 15 * Vytvoří na zadaných souřadnicích instanci se zadanou šířkou, výškou 16 * a poměrem velikosti kmene ku zbytku stromu. 17 * Vytvořené instanci přiřadí její "rodné číslo". 18 */ 19 public Strom( int x, int y, int šířka, int výška, 20 21 int podílŠířkyKmene, int podílVýškyKmene ) @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 190 z 433 Kapitola 4: Dotváříme vlastní třídu { 22 23 24 25 26 27 28 29 30 31 32 33 } 34 35 36 // ... 37 } 191 počet += 1; //Počet vytvořených instancí bude o jednu vyšší this.pořadí = počet; //Pořadové číslo vytvářené instance this.název = "Strom_" + pořadí; int výškaKmene = výška / podílVýškyKmene; int výškaKoruny = výška - výškaKmene; int šířkaKmene = šířka / podílŠířkyKmene; int posunKmene = ( šířka - šířkaKmene) / 2; koruna = new Elipsa ( x, y, šířka, výškaKoruny, Barva.ZELENÁ ); kmen = new Obdélník( x+posunKmene, y+výškaKoruny, šířkaKmene, výškaKmene, Barva.ČERVENÁ ); Vlastní přiřazení rodného čísla instance probíhá na řádcích 23 a 24. Na řádku 23 zvětšujeme hodnotu uloženou v atributu počet o jedničku a v řádku 24 pak tuto novou hodnotu přiřazujeme do proměnné pořadí jako rodné číslo dané instance. Na řádku 25 pak přiřazujeme hodnotu konstantě název. Doplňte podobným počítáním instancí i svoji vlastní třídu z minulé kapitoly. 4.5 Inkrementační a dekrementační operátory Velmi často potřebujeme v programu zvětšit nebo zmenšit hodnotu nějaké číselné proměnné o jedničku. Tato operace je tak častou, že se jí autoři jazyka rozhodli věnovat vlastní operátory. Pro přičtení jedničky (inkrementaci) používáme operátor ++ a pro její odečtení (dekrementaci) operátor --. Tyto operátory pracují pouze s jediným operandem – s tím, který mají zvětšit nebo zmenšit. Nazýváme je proto unární, na rozdíl od operátorů +, -, * a /, které pracují se dvěma operandy a označujeme je proto jako binární. Operátory ++ a -- mají jednu zvláštnost: můžeme si vybrat, umístíme-li je před anebo za upravovaný operand. V prvním případě pak operátory označujeme jako @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 191 z 433 192 Myslíme objektově v jazyku Java 1.5 preinkrementační, resp. predekrementační, v druhém jako postinkrementační, resp. postdekrementační. Je-li operace jedinou operací v příkazu, tak se rozdíl v umístění operátoru nijak neprojeví. Je-li však tato operace součástí složitějšího výrazu, pak se u preinkrementačního operátoru nejprve přičte k proměnné jednička a ve výrazu se použije zvětšená proměnná, kdežto u postinkrementačního operátoru se proměnná nejprve použije ve výrazu a teprve pak se o jedničku zvětší. Úplně stejně je to i s dekrementací. Ukážeme si to na příkladech. V následující tabulce jsou v širokém levém sloupci uvedeny příkazy a ve třech úzkých sloupcích hodnoty proměnných po provedení příkazu vlevo. Příkaz int i=1, j=0, k=0; j = i++; k = ++i k = i-- + j--; k = --i + --j; i 1 2 3 2 1 j 0 1 1 0 -1 k 0 0 3 4 0 Zkuste si příkazy z levého sloupce provést nejprve sami a pak své výsledky porovnejte s hodnotami ve sloupcích vpravo. Pro ty, jejichž výsledky se budou lišit, uvádím pro jistotu malé vysvětlení: 1. V prvním řádku pouze přidělíme počáteční hodnoty, takže vpravo najdeme vypsané hodnoty, které jsme vlevo přiřadili. 2. Ve druhém řádku nejprve použijeme současnou hodnotu i (=1), tu přiřadíme do j, a pak hodnotu i zvětšíme o jedničku. 3. Ve třetím řádku naopak nejprve zvětšíme i, takže bude mít hodnotu 3, a tuto novou hodnotu přiřadíme do proměnné k. 4. Ve čtvrtém řádku sečteme současné hodnoty proměnných i (=3) a j (=1), výsledek uložíme do k. Hodnoty proměnných i a j po použití ještě zmenšíme o jedničku. 5. V posledním pátém řádku nejprve zmenšíme hodnoty proměnných i a j a tyto změněné hodnoty pak sečteme a výsledek přiřadíme do k. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 192 z 433 Kapitola 4: Dotváříme vlastní třídu 193 Definujte třídu Xkrementy a v ní statickou metodu test(), ve které ověříte, že inkrementační a dekrementační operátory fungují tak, jak popisuje tabulka. Přiřaďte vždy proměnným nové hodnoty a řetězec s provedenou operací a výslednými hodnotami zobrazte pomocí metody P.zprava(String). Nezapomeňte doplnit zdrojový kód o komentáře. Abyste to ale neměli tak jednoduché, tak vám to trochu zkomplikuji. Jak si možná vybavíte, již několikrát jsem říkal, že dobrý programátor neopisuje stejný kód. Popsané zadání si ale přímo koleduje o to, aby se opakovaně dělalo totéž. Takové situace se většinou řeší pomocnou metodou, v níž naprogramujeme opakovanou část programu a na potřebných místech programu pak tuto metodu pouze zavoláme. Pro jistotu jenom upozorňuji, že když jsou metody s opakujícím se kódem statické, musí být statická i pomocná metoda. A když je pomocná, tak by měla být také soukromá, protože nikomu není nic do toho, jaké pomocné metody ve třídě používám. Těm činorodým pak přidám ještě doplňující úkol: skládejte postupně jednotlivé tištěné řetězce „na hromadu“ a na závěr je ještě jednou vytiskněte všechny najednou. Doufám, že nebudete číst dál, dokud tento prográmek nevytvoříte. Je poměrně jednoduchý a chcete-li se opravdu naučit programovat, byla by ostuda, kdybyste se spokojili pouze s tím, že si prohlédnete následné vzorové řešení. Není určeno pro lenochy, ale pro ty, kteří knihu studují sami bez učitele a potřebují mít možnost si své programy s něčím porovnat. 1 /******************************************************************************* 2 * Třída má za úkol otestovat chování inkrementačních a dekrementačních 3 * operátorů. 4 */ 5 public class Xkrementy 6 { //i, j, k jsou definovány jako statické atributy, 7 //aby je metoda test mohla sdílet s metodou zobraz 8 private static int i, j, k; 9 10 //Atribut vše slouží jako akumulátor vytvářeného řetězce 11 12 private static String vše = ""; 13 14 /*************************************************************************** 15 * Metoda ověří, že se operátory chovají tak, 16 * jak je v učebnici uvedeno v tabulce. 17 */ 18 19 public static void test() @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 193 z 433 194 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 } Myslíme objektově v jazyku Java 1.5 { } //Statické atributy i = 1; j = i++; k = ++i; k = i-- + j--; k = --i + --j; P.zpráva( vše ); jsou na zobraz( zobraz( zobraz( zobraz( zobraz( počátku nulové => stačí nastavit i "Počáteční hodnoty"); "Po (j = i++)" ); "Po (k = ++i)"); "Po (k = i-- + j--)"); "Po (k = --i + --j)"); /*************************************************************************** * Pomocná metoda, která zobrazí poslední akci a * vytiskne pomocí metody P.zpráva() hodnoty všech číselných atributů. * Zaroveň přidá tištěný text do souhrnného řetězce, * který se bude tisknout na závěr. */ private static void zobraz( String text ) { text = text + ": i=" + i + ", j=" + j + ", k=" + k; P.zpráva( text ); vše = vše + "\n" + text; } Spustíte-li si metodu test z předchozího programu několikrát za sebou, zjistíte, že byla vlastně navržena jako metoda na jedno použití, protože předpokládá, že při jejím spuštění jsou nastaveny správně počáteční hodnoty statických atributů. Po ukončení metody však mají tyto atributy zcela jiné hodnoty, takže při příštím spuštění budou výsledky vypadat jinak. Pokud by nám takovéto chování nevyhovovalo a chtěli bychom, aby se metoda při každém spuštění chovala stejně, museli bychom na jejím počátku požadovaným způsobem inicializovat všechny atributy, které používá. Odbočka o obecných zásadách programování Nyní na chvilku odbočím od hlavního proudu výkladu a povím vám něco o obecných zásadách tvorby programů. Problémy, ně něž upozornila předchozí poznámka, jsou důsledkem rozhodnutí předávat si hodnoty mezi dvěma metodami prostřednictvím atributů. Předchozí program tak ušetřil programátorovi trochu toho psaní. Nicméně obecně se používání atributů pro předávání hodnot mezi metodami nedoporučuje, protože @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 194 z 433 Kapitola 4: Dotváříme vlastní třídu 195 narušuje zapouzdření – k těmto atributům totiž mají přístup i metody, které nemají s daným problémem nic společného. Následující prográmek ukazuje, jak je možno stejný problém řešit bez pomoci atributů za pomoci lokálních proměnných, které zapouzdření nenarušují (abychom neřešili dvakrát úplně stejnou úlohu, tak jsem proměnné i, j a k nedefinoval jako lokální proměnné, ale jako parametry). 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 /*************************************************************************** * Metoda ověří, že se operátory chovají tak, jak je v učebnici uvedeno * v tabulce. Metoda umožnuje zadání výchozích hodnot proměnných * a nepoužívá atributy, ale nahrazuje je lokálními proměnnými. */ public static void test( int i, int j, int k ) { String vše; i = 1; vše = zobraz( i, j, k, "Počáteční hodnoty"); j = i++; vše += '\n' + zobraz( i, j, k, "Po (j = i++)" ); k = ++i; vše += '\n' + zobraz( i, j, k, "Po (k = ++i)" ); k = i-- + j--; vše += '\n' + zobraz( i, j, k, "Po (k = i-- + j--)" ); k = --i + --j; vše += '\n' + zobraz( i, j, k, "Po (k = --i + --j)" ); P.zpráva( vše ); } /*************************************************************************** * Pomocná metoda, která zobrazí poslední akci a * vytiskne pomocí metody P.zpráva() hodnoty všech číselných parametrů. * * @return Vytištěný řetězec. */ private static String zobraz( int i, int j, int k, String text ) { text = text + ": i=" + i + ", j=" + j + ", k=" + k; P.zpráva( text ); return text; } Jak vidíte, řešení je poněkud komplikovanější a pro mnohé asi také méně přehledné. Plyne z toho jedna rada: hlavní zásadou programátora by mělo být, aby jeho programy byly funkční a co nejpřehlednější. Bude-li po porušení některé z obecných zásad program přehlednější a čitelnější, neostýchejte se tuto zásadu porušit. Obě verze řešení úlohy (tj. s parametry i bez nich) najdete ve třídě XkrementyRUP, kterou si můžete stáhnout z projektu 03_Třídy_Z. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 195 z 433 196 Myslíme objektově v jazyku Java 1.5 Jiný způsob inicializace rodného čísla S využitím operátoru preinkrementace bychom mohli inicializovat atribut pořadí již v deklaraci a vůbec bychom s tím nemuseli zatěžovat tělo konstruktoru. Nová podoba deklarace by pak měla tvar: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class Strom { // ... Nezobrazená část programu /** Počet doposud vytvořených instancí. */ private static int počet = 0; /** Pořadí kolikátá byla daná instance vytvořena v rámci třídy. */ private final int pořadí = ++počet; /** Název sestávající z názvu třídy a pořadí instance */ private final String název = "Strom_" + pořadí; // ... Nezobrazená část programu } V deklaraci na řádku 9 preinkrementační operátor nejprve zvětší o jedničku hodnotu uloženou v atributu počet a tato zvětšená hodnota se pak přiřadí atributu pořadí. 4.6 Standardní výstup Doposud jsme pro všechny naše výstupy používali statické metody pomocné třídy P, které zobrazili textový řetězec v dialogovém okně. Takovýto výstup sice vypadá komfortně, avšak v některých situacích není optimální. Příkladem situace, kdy se nám postupný výstup do oken zrovna nehodil, byla poslední úloha, kdy jsem po vás chtěl, abyste na závěr vytiskli všechny příklady pěkně pohromadě. Ve vzorovém programu jsem vám sice ukázal, jak byste si mohli poradit, ale existovala i jiná řešení. Jedním z nich je postupné posílání vystupujících řetězců na standardní výstup. K tomu slouží metoda print objektu out, který je veřejným statickým atributem třídy System. Tato metoda požaduje jeden parametr, který však může být libovolného typu. Tento parametr převede na řetězec a řetězec pošle na standardní výstup. Protože většina požadavků tisku na standardní výstup končí odřádkováním, nabízí objekt out ještě metodu println, která po vytištění řetězce sama odřádkuje. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 196 z 433 Kapitola 4: Dotváříme vlastní třídu 197 Ukažme si použití těchto metod opět na příkladu. Doplníme předchozí třídu o statickou metodu ttest (tištěný test), která bude řešit stejný problém, ale místo pomocné metody zobraz(String) použije pomocnou metodu tiskni(String), která vytiskne zadaný řetězec na standardní výstup. Abych jenom neopisoval dřívější řešení, upravil jsem výstup tak, aby metoda ttest byla ještě stručnější: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 /*************************************************************************** * Bezparametrická verze testu, která své výsledky nezobrazuje * v dialogovém okně, ale posílá je na standardní výstup. */ public static void ttest() { System.out.println("\n==============================================="); i = 1; tiskni( "start"); j = i++; tiskni( "j = i++" ); k = ++i; tiskni( "k = ++i"); k = i-- + j--; tiskni( "k = i-- + j--"); k = --i + --j; tiskni( "k = --i + --j"); } /*************************************************************************** * Pomocná metoda, která vytiskne zadanou akci na standarní výstup. */ private static void tiskni( String akce ) { //Dvojice znaků \t v následujícím příkazu označuje znak tabulátoru System.out.println( "Po (" + akce + "):\ti=" + i + ", j=" + j + ", k=" + k ); } Výše definovanou dvojici metod najdete na konci těla dříve stáhnuté třídy XkrementyRUP. Po spuštění metody ttest() otevře BlueJ další, doposud neotevřené okno, kam vypisuje vše, co pošlete na standardní výstup. Toto okno můžete kdykoliv zobrazit zadáním příkazu Zobrazit → Terminál. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 197 z 433 198 Myslíme objektově v jazyku Java 1.5 Obrázek 4.6 Okno terminálu zobrazující standardní výstup metody ttest ☺ Budete-li chtít, aby se vám tisky z minulých spouštění svých programů nemíchaly s stisky z nových spouštění, máte dvě možnosti: v okně terminálu zadáte příkaz Nastavení → Smazat, na výstup pošlete znak pro odstránkování (form feed) – např. jako řetězec "\f". 4.7 Metoda toString() V pasáži Textové řetězce na straně 177 jsme hovořili o tom, že každou instanci je možno převést na řetězec a že k tomu slouží metoda toString(). Doplňme nyní naši třídu její vlastní metodou toString(), aby nemusela být odkázána na metody získané „od cizích“. Nedefinujete-li vlastní verzi metody toSAtring(), použije se verze definovaná systémem. Ta vrátí řetězec, který začíná názvem třídy, jejíž instanci na textový řetězec převádíme, následovaný znakem @ (šnek, zavináč) a číslem zapsaným v šestnáctkové soustavě. Někdy takovýto převod vyhovuje, ale většinou budeme chtít definovat vlastní, který by pro nás byl více informativní. Dohodněme se, že metoda toString() instancí třídy Strom se bude chovat stejně jako obdobné metody tříd grafických obrazců, tj. že vypíše jméno třídy, pořadí in- @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 198 z 433 Kapitola 4: Dotváříme vlastní třídu 199 stance a za ním aktuální souřadnice a rozměr dané instance. Výsledný program by mohl vypadat např. následovně: 1 2 3 4 5 6 7 8 9 10 11 /*************************************************************************** * Převede instanci na řetězec obsahující název třídy, pořadí instance, * její souřadnice a rozměry. * * @return Řetězcová reprezentace dané instance. */ public String toString() { return "Strom_" + pořadí + ": x=" + getX() + ", y=" + getY() + ", šířka=" + getŠířka() + ", výška=" + getVýška(); } Metodu hned vyzkoušíme. Vytvořte novou instanci třídy Strom pomocí bezparametrického konstruktoru a pak zavolejte metodu zpráva(text) třídy P a předejte jí tuto instanci jako parametr. Máte-li vše naprogramováno správně, metoda otevře dialogové okno z obr. 4.7. Obrázek 4.7 Řetězec, na nějž převede instanci třídy Strom metoda toString, vytištěný v okně Doplňte metodou toString i svoji vlastní třídu z minulé kapitoly. Podoba třídy Strom, do které jsme ji doposud dovedli, je uložena ve třídě Strom_4, kterou najdete v projektu 03_Třídy_Z. 4.8 Prázdná standardní třída Ještě jednou se vrátím k vytváření nové třídy. Doposud jsme při jejím vytváření nastavovali přepínač do polohy Prázdná třída. Od této chvíle však budu využívat šablony, po níž BlueJ sáhne při nastavení přepínače druhu vytvářené třídy do polohy Standardní třída. V takovém případě BlueJ otevře soubor, v němž již bude série @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 199 z 433 200 Myslíme objektově v jazyku Java 1.5 řádkových komentářů naznačujících doporučované pořadí vkládaných atributů a metod, atributy počet a pořadí generující „rodné číslo“ vytvářené instance a budou zde zároveň připraveny prázdné definice bezparametrického konstruktoru a metody toString(). Připomínám, že uvedenou podobu dále uvedené standardní prázdné třídy mohou využívat pouze ti, kdo si instalovali modul rup. Standardní prázdná třída dodávaná s originální instalací vypadá trochu jinak. Máte-li instalovaný modul rup a vytvoříte-li standardní třídu s názvem PrázdnáTřída, obdržíte následující program. Projděte si jej a připomeňte si přitom vše, co jsme doposud probrali. Nevzrušujte se prozatím komentáři, které hovoří o věcech, jež jsme ještě neprobrali – postupně si o všech povíme. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 /******************************************************************************* * Třída PrázdnáTřída slouží k ... * * @author jméno autora * @version 0.00.000, 0.0.2003 */ public class PrázdnáTřída { //== KONSTANTNÍ ATRIBUTY TŘÍDY ================================================= //== PROMĚNNÉ ATRIBUTY TŘÍDY =================================================== /** Celkový počet vytvořených instancí. */ private static int počet = 0; //== KONSTANTNÍ ATRIBUTY INSTANCÍ ============================================== /** Rodné číslo instance = jako kolikátá byla vytvořena. */ private final int pořadí = ++počet; //== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================ //== PŘÍSTUPOVÉ METODY ATRIBUTŮ TŘÍDY ========================================== //== OSTATNÍ METODY TŘÍDY ====================================================== //############################################################################## //== KONSTRUKTORY A METODY VRACEJÍCÍ INSTANCE VLASTNÍ TŘÍDY ==================== /*************************************************************************** * Bezparametrický konstruktor ... */ public PrázdnáTřída() { } //== PŘÍSTUPOVÉ METODY ATRIBUTŮ INSTANCÍ ======================================= @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 200 z 433 Kapitola 4: Dotváříme vlastní třídu 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 201 //== PŘEKRYTÉ METODY IMPLEMENTOVANÝCH ROZHRANÍ ================================= //== PŘEKRYTÉ ABSTRAKTNÍ METODY RODIČOVSKÉ TŘÍDY =============================== //== PŘEKRYTÉ KONKRÉTNÍ METODY RODIČOVSKÉ TŘÍDY ================================ /*************************************************************************** * Vrací textovou reprezentaci dané instance * používanou především k ladícím účelům. * * @return Název třídy následovaný podtržítkem a "rodným číslem" instance. */ public String toString() { return getClass().getName() + "_" + pořadí; //return P.jménoTřídy( this ) + "_" + pořadí; } //== NOVĚ ZAVEDENÉ METODY INSTANCÍ ============================================= //== SOUKROMÉ A POMOCNÉ METODY TŘÍDY =========================================== //== SOUKROMÉ A POMOCNÉ METODY INSTANCÍ ======================================== //== VNOŘENÉ A VNITŘNÍ TŘÍDY =================================================== //== TESTY A METODA MAIN ======================================================= }//public class PrázdnáTřída Na řádcích 1 – 6 je dokumentační komentář třídy. Zde vyplňte funkci a účel třídy a u složitějších tříd zde můžete dodat i nějaký pomocný výklad k jejich používání. Komentáře na řádcích 9, 10 a 16 označují příslušné sekce atributů. Atribut počet na řádku 13 slouží k uchování celkového počtu doposud vytvořených instancí. Je spřažen s atributem pořadí (řádek 19), v němž se uchovává číslo, označující kolikátá je daná instance mezi doposud vytvořenými instancemi. To už jsme si všechno ukazovali. Komentáře na řádcích 22 – 24 označují další sekce zdrojového kódu. O všech z nich jsme si již vyprávěli. Na řádku 27 vás možná zarazilo, že vedle konstruktorů jsou i jiné metody vracející odkaz a instance této třídy. Nicméně jste se s podobnou metodou již setkali – je jí metoda getPlátno ve třídě Plátno. Komentáře na řádcích 37 až 40 hovořící o překrytých metodách zatím ignorujte. Jejich význam si budeme postupně objasňovat v následujících kapitolách. Tělo definice metody toString() vás možná zarazí. Je upravené tak, aby pracovalo vždy nezávisle na tom, máte-li v projektu definovanou třídu P s jejími @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 201 z 433 202 Myslíme objektově v jazyku Java 1.5 statickými metodami. Definici využívající třídu P získáte tak, že první řádek (50) smažete a zrušíte komentářová lomítka na počátku druhého řádku (51). Zbylé komentáře by vám měly být jasné. Jedinou výjimkou je komentář na řádku 58 hovořící o vnořených a vnitřních třídách. Na ty dojde až v samém závěru učebnice. Jak jsem již řekl, všechny programy, které jsem pro vás a tuto učebnici připravil, jsou vytvořeny pomocí této šablony. Pokud vám pořadí deklarací a definic nesedí, můžete si zvolit jiné. Určitě vám však doporučuji si nějaké pořadí zavést a zvyknout si na ně. I takové jednoduché třídy, jakou je např. náš strom, mívají dlouhé zdrojové kódy s řadou atributů a metod a zavedení obdobného pořadí může urychlit jejich vyhledávání. 4.9 V útrobách testovací třídy V podkapitole Testování na straně 104 jsme se naučili vytvářet testovací třídy obsahující testovací přípravky a testy, které si vždy nejprve nechali připravit přípravek a nad ním pak test provedly. Zároveň jsme si zde řekli, že v moderním programování se začíná stále více prosazovat metodika TDD, podle níž je třeba nejprve napsat testy a teprve pak vlastní program. Vraťme se ale k testovacím třídám prostředí BlueJ. Testovací třída je obyčejná třída jako každá jiná. Pravda, používá některé technologie, které jsme si prozatím nevysvětlili, ale jinak na ní není nic zvláštního. Jediná zvláštní je, že ji za nás BlueJ připravil automaticky, takže jsme ji nemuseli programovat. Často se vyskytují situace, kdy pomocí takovéto automaticky vytvořené třídy nedokážeme dost dobře definovat test, který by splňoval všechny naše požadavky, a je proto vhodné mu pomoci „ručně“. K tomu ale potřebujeme vědět, jak vlastně taková testovací třída pracuje. Vytvoříme si nyní proto novou, prázdnou testovací třídu a vysvětlíme si na ní mechanizmy, které automatické testování umožňují, a ukážeme si také, jak BlueJ testy vytváří. Testovací třídu můžeme vytvořit dvěma způsoby: v místní nabídce třídy, kterou budete chtít testovat, zadáte příkaz Vytvořit testovací třídu, požádáte o vytvoření nové třídy (např. stisknutím tlačítka Nová třída) a v následně otevřeném dialogovém okně nastavíte přepínač do polohy Test jednotky pro BlueJ. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 202 z 433 Kapitola 4: Dotváříme vlastní třídu 203 V obou případech se vytvoří testovací třída podle stejné šablony. Rozdíl bude pouze v tom, že při vytváření nové třídy musíte zadat název testovací třídy vy, kdežto třída vytvořená z místní nabídky jiné třídy odvodí svůj název od názvu této třídy (viz naše třída SmrkTest) a navíc se v diagramu tříd bude snažit této třídy „držet“. (Zkuste s testovanou třídou v diagramu zahýbat a uvidíte, jak ji bude testující třída následovat.) Třídám vytvořeným klasickým způsobem musíte vymyslet název sami a nezávisle na názvu se na žádnou jinou třídu „nelepí“. Jinak s nimi ale můžete dále pracovat zcela stejně jako s třídami vytvořenými z místní nabídky. „Přilepené“ testovací třídy získáte opravdu pouze zadáním příkazu v místní nabídce testované třídy. Získáte-li třídu jakkoliv jinak, už se na testovanou třídu lepit nebude, a to ani tehdy, převezmete-li obě třídy (tj. testovanou a testovací) z jiného projektu, kde se na sebe „lepí“. Dost již ale doprovodných řečí a pojďme vytvořit plánovanou testovací třídu. Abychom viděli shodnosti a rozdíly, vytvoříme ji nezávisle na ostatních třídách: stiskněte tlačítko Nová třída a zadejte, že chcete vytvořit Test jednotky pro BlueJ nazvaný Test_04. Otevřete nyní soubor s jejím zdrojovým kódem a prohlédněte si jej. Měl by vypadat následovně: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 /******************************************************************************* * Testovací třída Test_04. * * @author jméno autora * @version 0.00.000, 0.0.2003 */ public class Test_04 extends junit.framework.TestCase { //############################################################################## //== PŘÍPRAVA A ÚKLID PŘÍPRAVKU ================================================ /*************************************************************************** * Vytvoření přípravku (fixture), tj. sady objektů, s nimiž budou všechny * testy pracovat a která se proto vytvoří před spuštěním každého testu. */ protected void setUp() { } /*************************************************************************** * Úklid po testu - tato metoda se spustí po vykonání každého testu. */ protected void tearDown() { @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 203 z 433 204 Myslíme objektově v jazyku Java 1.5 26 } 27 28 29 //== VLASTNÍ TESTY ============================================================= 30 }//public class Test_04 extends junit.framework.TestCase Třída obsahuje připravený konstruktor a dvě prázdné metody: setUp() a tearDown(). Jak si můžete přečíst v komentářích, metoda setUp() má na starosti vytvoření přípravku před spuštěním vlastního testu a metoda tearDown() pak má za úkol po skončení testu „uklidit“. Přípravek Jak jistě odhadnete, kód potřebný pro definici přípravku vkládá BlueJ do metody setUp(), která je doposud prázdná. Vyzkoušejte si to. Chcete-li získat stejný zdrojový kód jako já, postupujte následovně: 1. Restartujte virtuální stroj. 2. Pošlete třídě Plátno zprávu getPlátno() a získaný odkaz uložte do zásobníku odkazů. 3. Vytvořte nový obdélník pomocí bezparametrického konstruktoru. 4. Vytvořte novou elipsu na souřadnicích x=0, y=50 s rozměry šířka=50, výška=25. 5. Vytvořte nový trojúhelník na souřadnicích x=50, y=50 s rozměry šířka=50, výška=50. 6. Změňte rozměr obdélníku na šířka=50, výška=25. 7. Změňte pozici trojúhelníku na x=0, y=50. 8. Požádejte třídu Test_04, aby ze zásobníku odkazů vytvořila testovací přípravek. Podíváte-li se nyní na zdrojový kód třídy, zjistíte dvě změny: na počátku těla třídy se objevily deklarace atributů, tělo metody setUp() bylo naplněno kódem definujícím vytvoření potřebných instancí a ukládajícím odkazy na tyto instance do připravených atributů. 1 public class Test_04 extends junit.framework.TestCase 2 { 3 private Plátno plátno1; @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 204 z 433 Kapitola 4: Dotváříme vlastní třídu 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 205 private Obdélník obdélník1; private Elipsa elipsa1; private Trojúhelník trojúhel1; //############################################################################## //== PŘÍPRAVA A ÚKLID PŘÍPRAVKU ================================================ /*************************************************************************** * Přípravek (fixture) - inicializace spouštěná před každým testem. */ protected void setUp() { plátno1 = Plátno.getPlátno(); obdélník1 = new Obdélník(); elipsa1 = new Elipsa(50, 0, 50, 25); trojúhel1 = new Trojúhelník(50, 50, 50, 50); obdélník1.setRozměr(50, 25); trojúhel1.setPozice(50, 0); }//protected void setUp() /*************************************************************************** * Úklid spouštěný po každém testu. */ protected void tearDown() { }//protected void tearDown() //== VLASTNÍ TESTY ============================================================= }//public class Test_04 extends junit.framework.TestCase V postupu, podle kterého jste připravovali obsah zásobníku odkazů, jsem schválně postupoval „neoptimálně“ a některé parametry vytvářených instancí dodatečně upravoval. Chtěl jsem vám ukázat, že při vytváření přípravku BlueJ postupuje tak, že do metody setUp() zanese postupně všechny příkazy, které jste před tím zadávali. Z předchozího výkladu je vám již asi jasné, proč musíme před vytvořením sady odkazů určených k uložení do přípravku restartovat virtuální stroj. Požádáte-li BlueJ o vytvoření přípravku až po delším experimentování, zanese do metody setUp() záznam o veškerém tomto experimentování. Je proto výhodné po předchozích experimentech před přípravou požadované podoby zásobníku odkazů nejprve restartovat virtuální stroj. BlueJ pak zanese pouze akce provedené po tomto restartu. Je zřejmé, že současná podoba metody setUp() je zbytečně složitá. Nic nám proto nebrání ji zjednodušit. Smažte v těle metody poslední dva příkazy a upravte @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 205 z 433 206 Myslíme objektově v jazyku Java 1.5 předchozí volání konstruktorů tak, abychom požadovanou podobu přípravku získali hned po konstrukci jednotlivých objektů. Upravená metoda by měla vypadat následovně: 1 protected void setUp() 2 { 3 plátno1 = Plátno.getPlátno(); 4 obdélník1 = new Obdélník(0, 0, 50, 25); 5 elipsa1 = new Elipsa(50, 0, 50, 25); 6 trojúhel1 = new Trojúhelník(50, 0, 50, 50); 7 } Vytvořený přípravek můžete upravovat nejenom ručně, ale i s využitím podpory BlueJ tak, jak jsme si to ukazovali v kapitole Testování. Budete-li chtít přípravek později doplnit, můžete to udělat tak, že (po restartu VM) provedete všechny požadované akce včetně případného naplnění zásobníku přípravkem a dodání dalších odkazů a výslednou podobu necháte uložit jako přípravek (to už jsme si ukazovali v kapitole Úprava obsahu přípravku na straně 107). Když si po této změně prohlédnete obsah metody setUp(), najdete v ní nový seznam příkazů, přičemž na místě, kde jste naplňovali zásobník odkazů minulým přípravkem, budou opsány příkazy z těla minulé verze metody setUp(). Vyzkoušejte si to. Automaticky generované testy Podívejme se nyní, co se stane, když vytvoříme nějaký automaticky generovaný test. Požádejte třídu Test_04 o vytvoření nového testu nazvaného ZměnaVelikostiPlátna, v němž zmenšíte plátno na rozměr šířka=100, výška=200 a necháte znovu zobrazit všechny instance. Když ukončíte záznam testu a podíváte se do zdrojového kódu třídy Test_04, zjistíte, že se na jeho konci objevila nová metoda: 1 public void testZměnaVelikostiPlátna() 2 { 3 plátno1.setRozměr(100, 200); 4 obdélník1.nakresli(); 5 elipsa1.nakresli(); 6 trojúhel1.nakresli(); strom1.nakresli(); 7 8 } @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 206 z 433 Kapitola 4: Dotváříme vlastní třídu 207 Stejně jako u přípravku platí, že tento automaticky vygenerovaný kód můžete jakkoliv upravit. BlueJ není nijak ješitný a budete-li jeho výtvory upravovat, nebude se bouřit. Vlastní testy Rozhodnete-li se jednou upravovat zdrojový kód testovací třídy, nemusíte se omezovat pouze na úpravu automaticky generovaných testů. Nic vám nebrání vytvářet testy vlastní. Má-li však BlueJ chápat vámi napsaný test opravdu jako text, musíte při psaní testovací metody dodržet několik pravidel: její jméno musí začínat předponou test a pokračovat názvem testu, musí být veřejná, nesmí mít žádné parametry. Při tvorbě testu musíte zároveň počítat s tím, že se před spuštěním vlastního testu nejprve spustí metoda setUp(). Definujme jednoduchý test, který nazveme testProhození a který převrátí umístění jednotlivých obrazců na plátně – pravé prohodí s levými a horní s dolními. Vše potřebné již znáte, takže si můžete zkusit naprogramovat test nejprve sami. Tak co, funguje? Pokud ne, zkuste jej nejprve rozchodit. Pokud vám již funguje (a nebo jenom nemáte trpělivost hledat chyby), zkuste svůj návrh porovnat s následujícím řešením (pokud se liší, nevadí – hlavně, že funguje): 1 public void testProhození() 2 { 3 //Aby bylo prohození vidět co nejlépe, vypíšeme nejprve upozornění P.zpráva("Bude se prohazovat"); 4 5 //Nejprve je třeba některé obrazce přesunout jinam, 6 //aby se při přesunech vzájemně neumazávali. 7 8 //To, že se budou v odkladné pozici překrývat, nevadí. 9 obdélník1.setPozice(200,0); 10 elipsa1.setPozice(200,0); trojúhel1.setPozice(200, 0); 11 //Strom je poslední - proto jej již nikdo nepřemaže 12 13 //Nyní přesuneme obrazce do cílových pozic 14 15 strom1.setPozice(50,0); obdélník1.setPozice(50,150); 16 17 elipsa1.setPozice(0, 50); trojúhel1.setPozice(0, 0); 18 19 } @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 207 z 433 208 Myslíme objektově v jazyku Java 1.5 Úklid Zásobník odkazů a některé další objekty je třeba nejenom před testem připravit, ale také po testu uklidit. Spustíte-li náš test dvakrát po sobě, budou při upozornění na chystané prohození na plátně dvě sady obrazců, což určitě není to, co bychom chtěli. Do obdobně nepříjemného stavu se dostanete i tehdy, když spustíte nejprve test změny velikosti plátna a po něm test prohození. Je to proto, že po sobě naše testy neuklízely. Hned to napravíme. K úklidu po provedeném testu slouží metoda tearDown(), kterou systém spustí automaticky po každém testu. V našem případě, kdy účelem testů je ukázat na plátně, že se obrazce chovají tak, jak mají, ale nemůžeme uvést plátno do počátečního stavu hned po provedení testu, protože by uživatel neměl možnost si výsledný obrázek prohlédnout a ověřit, že výsledek odpovídá očekávání. Musíme proto před vlastní úklid ještě předřadit dotaz, který uživateli umožní zkontrolovat výsledek. Výsledný uklízecí program by pak mohl vypadat např. následovně: 1 protected void tearDown() 2 { assertTrue( P.souhlas("V pořádku?") ); 3 plátno1.setRozměr(300, 300); 4 5 } Metody assertEquals a assertTrue V předchozím programu jsme výsledek dotazu na to, zda je vše v pořádku, předa-li jako parametr metodě assertTrue(boolean). Tato metoda je jednou ze série potvrzovacích metod, kterými v průběhů našich testů ověřujeme, že je vše v pořádku. Základními potvrzovacími metodami jsou metody assertEquals(x1,x2), kterých je celá řada a liší se pouze tím, jakého typu jsou jejich parametry. Těmto metodám předáme v prvním parametru očekávanou hodnotu a ve druhém hodnotu, ke které dospěl program. Pokud se tyto hodnoty liší, metody vypíší v testovacím okně očekávanou a obdrženou hodnotu a daný test ukončí. V programu použitá metoda assertTrue(boolean) je vlastně jenom zestručněnou verzí metody assertEquals(boolean, boolean), které předáváme jako první parametr (tj. jako očekávanou hodnotu) logickou hodnotu true. Místo assertEquals( true, obdrženýVýsledek ); tak můžeme zapsat pouze @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 208 z 433 Kapitola 4: Dotváříme vlastní třídu 209 assertTrue( obdrženýVýsledek ); Moc psaní nám to neušetří, ale leckomu (např. mně) připadá takový zápis přehlednější. Dvouparametrické verze metod mají ještě tříparametrické kolegyně, které očekávají jako první parametr textový řetězec, za nímž následují testované hodnoty. Tyto metody v případě neshody testovaných hodnot vypíší do testovacího okna nejprve zadaný řetězec, a teprve za ním zprávu o nesouhlasu testovaných hodnot. Test testů Abyste lépe pochopili význam testovacích metod, připravil jsem pro vás třídu TestTestů (najdete ji v projektu 06_Rozhraní_Z), která vám použití potvrzovacích metod předvede. Podívejme se, co vše nám může ukázat. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 /******************************************************************************* * Testovací třída TestTestů sloužící k demonstraci základních vlastností testů. * * @author Rudolf Pecinovský * @version 2.01, duben 2004 */ public class TestTestů extends junit.framework.TestCase { private int i1; private String s1; private Elipsa e; //############################################################################## //== PŘÍPRAVA A ÚKLID PŘÍPRAVKU ================================================ /*************************************************************************** * Vytvoření přípravku (fixture), tj. sady objektů, s nimiž budou všechny * testy pracovat, a která se proto vytvoří před spuštěním každého testu. */ protected void setUp() { System.out.println( "\n=== Příprava testu " + getName() ); i1 = 1; s1 = "Jedna"; e = new Elipsa(); } /*************************************************************************** * Úklid po testu - tato metoda se spustí po vykonání každého testu. */ protected void tearDown() { Plátno.smaž(); System.out.println( "XXX Uklizeno po testu " + getName() ); @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 209 z 433 210 Myslíme objektově v jazyku Java 1.5 36 } 37 38 39 //== VLASTNÍ TESTY ============================================================= 40 public void testČísel() 41 { 42 System.out.println( "Čísla souhlasí" ); 43 44 assertEquals( "Neshoda celého čísla", 1, i1 ); System.out.println( "Čísla nesouhlasí" ); 45 assertEquals( "Neshoda celého čísla", 0, i1 ); 46 System.out.println( "Konec testu čísel" ); 47 } 48 49 public void testSouřadnic() 50 51 { System.out.println( "Souřadnice souhlasí" ); 52 53 assertEquals( "Neshoda souřadnic", 0, e.getX() ); System.out.println( "Souřadnice nesouhlasí" ); 54 assertEquals( "Objekty se liší", null, e ); 55 assertEquals( "Neshoda souřadnic", 1, e.getX() ); 56 System.out.println( "Konec testu souřadnic" ); 57 58 } 59 60 public void testŘetězců() { 61 System.out.println( "Řetězce souhlasí" ); 62 63 assertEquals( "Neshoda textů", "Jedna", s1 ); System.out.println( "Řetězce souhlasí" ); 64 65 assertEquals( "Neshoda textů", "Dva", s1 ); System.out.println( "Konec testu řetězců" ); 66 67 } 68 69 }//public class TestTestů extends junit.framework.TestCase Na počátku (řádky 9-11) jsem deklaroval tři atributy, jimž metoda setUp() přiřadí hodnoty, které pak budeme v testovacích metodách testovat. Metoda setUp() na řádcích 20 až 26 nejenom vytvoří přípravek (tj. inicializuje potřebné proměnné), ale před tím nejprve vypíše na standardní výstup, že test je připraven. Využije přitom toho, že může zavoláním metody getname() zjistit, jak se test jmenuje. Obdobně metoda tearDown() na řádcích 32 až 36 nejenom po testu uklidí, ale současně také na závěr vypíše, že je již uklizeno. Na řádcích 41 až 67 jsem pro vás připravil tři testy. Každý z nich nejprve vypíše na standardní výstup, že začal, pak dvakrát vypíše zprávu o chystané akci následovanou voláním potvrzovací metody. Poprvé na dvě hodnoty, které jsou shodné, podruhé na hodnoty, které se liší. Prozatím jsou všechny tři testy nastaveny tak, aby v polovině zkolabovaly na rozdílnosti hodnot. Spustíte-li testy po resetovaném virtuálním stroji, získáte výpis, který si můžete prohlédnout na obr. 4.8. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 210 z 433 Kapitola 4: Dotváříme vlastní třídu 211 Obrázek 4.8 Kontrolní tisky posílané testem testů na standardní výstup Chtěl jsem vám na tomto příkladu znovu připomenout, že neprojití jednoho testu neovlivní chod testů dalších. Změnou očekávaných hodnot můžete dosáhnout toho, že celý test projde nebo naopak zkolabuje hned na první potvrzovací metodě. 4.10 Debugger a práce s ním Známá programátorská zásada říká, že v každém programu je nejméně jedna chyba. Případy, kdy je programátorovi při výskytu chyby ihned jasné, co je třeba opravit, však nebývají příliš časté. Mnohem častější je případ, kdy mu vůbec není jasné, co danou chybu způsobuje, a musí proto chvíli hledat, než se mu podaří chybu najít a opravit. Při tomto hledání programátoři nejčastěji používají dvě techniky: vkládají do programu příkazy, které jim někam vypíší aktuální stav programu (nejčastěji hodnoty atributů a lokálních proměnných), používají pomocný program zvaný debugger, který jim umožní program krokovat (provádět jej příkaz za příkazem) a přitom sledovat, co vlastně dělá. My se v této podkapitole zaměříme právě na onu druhou techniku a ukážeme si, co nám v tomto směru BlueJ nabízí. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 211 z 433 212 Myslíme objektově v jazyku Java 1.5 Krokování programu Otevřete si zdrojový kód třídy Test_04 a zobrazte tělo metody setUp(). Klepněte ukazatelem myši do levého sloupce, kde se zobrazují čísla řádků (postup je stejný i v případě, kdy čísla řádků nezobrazujete) do řádku, ve kterém se konstruuje elipsa (na obrázku 4.9 řádek 81). Obrázek 4.9 Nastavení zarážky na řádku 81 BlueJ vloží do daného místa programu zarážku (anglicky breakpoint) a v editoru v daném místě zobrazí malou ikonu symbolizující dopravní značku Stůj, dej přednost v jízdě. Až bude program procházet tímto místem, virtuální stroj zastaví jeho provádění a BlueJ nám zobrazí místo v programu, na němž se nacházíme, spolu s informacemi oé aktuálních hodnotách atributů a lokálních proměnných. Vše si hned vyzkoušíme. Vraťte se do okna projektu a spusťte test prohození. Program se rozběhne, zobrazí plátno, na něm obdélník a pak se zastaví a zobrazí svůj aktuální stav. Vzhled části obrazovky mého počítače se všemi čtyřmi zobrazenými okny si můžete prohlédnout na obr. 4.10. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 212 z 433 Kapitola 4: Dotváříme vlastní třídu 213 Obrázek 4.10 Vzhled části obrazovky po zastavení programu na zarážce Okna na obrázku jsem uspořádal tak, aby se vešla na obrazovku o rozměrech 800×600 bodů. Používáte-li vyšší rozlišení (a při programování vám to vřele doporučuji), můžete zařídit, aby se vám jednotlivá okna překrývala méně nebo dokonce vůbec. Vraťme se ale k obrázku a povězme si, co je na něm k vidění nového. Vykukující roh plátna i část okna projektu nám zatím nic nového neřeknou. Soustředíme se proto na zbylá dvě okna. V okně editoru vidíte zobrazenou část kódu, kde se program při svém provádění zarazil. Abyste to místo dobře poznali, tak BlueJ příslušný řádek podšedí a ve sloupečku s čísly řádku překryje ikonu zarážky černou šipkou. Kromě toho se otevřelo okno s titulkem BlueJ: Debugger, se kterým jsme se doposud nesetkali. Na toto okno zaměříme svoji další pozornost. Při jeho spodním okraji najdete pět velkých tlačítek: @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 213 z 433 214 Myslíme objektově v jazyku Java 1.5 Tlačítko Zastavit slouží k zastavení běžícího programu. Pokud se vám diagram např. zacyklí, tak jej můžete tímto tlačítkem zastavit a podívat se, kde se toulá. Stiskem tlačítka Krokovat provedete daný příkaz. Je-li na řádku se zarážkou více příkazů, provedou se všechny z nich. Je-li součástí příkazu volání metod, metody se provedou. Tlačítko Krokovat dovnitř má skoro stejnou funkci jako tlačítko Krokovat. Rozdíl se projeví až ve chvíli, když je součástí příkazu volání metod. Po stisku tohoto tlačítka se totiž metoda neprovede celá. Pouze se zavolá a provádění se zarazí na prvním příkazu těla volané metody. Tlačítko Pokračovat použijete ve chvíli, kdy již víte vše, co jste se chtěli od dané části kódu dozvědět. Po jeho stisku se zruší krokování a program pokračuje ve svém normálním běhu až do konce či do příští zarážky. Stiskem tlačítka Ukončit ukončíte provádění programu. Použijete je např. ve chvíli, když jste již zjistili, kde je chyba a chcete ji opravit. (Horší variantou je, že si uvědomíte, že jste přeběhli kritické místo a musíte program spustit znovu, abyste příště zastavili před ním.) Takže víme, co potřebujeme a můžeme se pustit do krokování. Stiskněte tlačítko Krokovat a ověřte, že se příslušný příkaz ihned provedl a na plátno se nakreslila elipsa. Znak zarážky zůstal tam, kde byl, ale šipka se spolu s podšedění přesunula na další řádek. Stiskněte znovu tlačítko Krokovat a přidejte na plátno trojúhelník, který se nakreslí přes elipsu. Šipka označující aktuální řádek se přesune na řádek s příkazem pro vytvoření instance stromu. Na tento příkaz si posvítíme trochu podrobněji a podíváme se, jak se vlastně provádí. Stiskněte proto tlačítko Krokovat dovnitř. Program se v tuto chvíli dozvěděl, že bude potřebovat pracovat s třídou Strom – musí si proto třídu nejprve připravit. A protože jsme stiskem tlačítka Krokovat dovnitř projevili značnou zvědavost, budeme moci přípravu třídy sledovat. Třída Strom má tři atributy třídy. Prvé dva jsou konstanty, jejichž hodnotu dokázal překladač zjistit již při překladu, a proto s tím při přípravě třídy již „neotravuje“. Třetí atribut je ale běžným atributem a je mu proto potřeba přiřadit počáteční hodnotu. Debugger proto otevře okno se zdrojovým kódem třídy Strom a nastaví šipku na příkaz, který přiřazuje atributu krok počáteční hodnotu 50. Podíváte-li se do okna debuggeru, zjistíte, že v části určené pro atributy třídy jsou již všechny tři atributy uvedeny, ale atribut krok má prozatím přiřazenu po- @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 214 z 433 Kapitola 4: Dotváříme vlastní třídu 215 čáteční hodnotu 0. Správnou počáteční hodnotu získá až po provedení přiřazení uvedeného v deklaraci. Stiskněte tlačítko Krokovat. Tím se atributu krok přiřadí požadovaná hodnota. Protože je tím příprava třídy Strom ukončena, debugger vrátí řízení metodě, která chtěla udělat něco (konkrétně vytvořit instanci), kvůli čemuž musela být připravena třída. Šipka ukazující příkaz, který se má vykonat, se proto nepohnula. Jedinou změnou od stavu, kdy jsme se na ni dívali minule, je, že nyní je již třída připravena k použití a můžeme ji poslat zprávu – požádat ji o vytvoření instance. Znovu tedy stiskneme tlačítko Krokovat dovnitř. To nás znovu přesune do zdrojového kódu třídy Strom, avšak tentokrát již do těla konstruktoru. V těle je jediný příkaz, který volá čtyřparametrickou verzi konstruktoru. Stiskem Krokovat dovnitř se přesuneme do jeho těla a tam akci ještě jednou zopakujeme. Tím se konečně dostaneme do těla konstruktoru, který již nikoho jiného za sebe pracovat neposílá a udělá vše potřebné sám (viz obr. 4.11). Obrázek 4.11 Po několika vnořeních jsme se přesunuli do těla konstruktoru, který provede všechny potřebné akce spojené s konstrukcí instance Okno debuggeru Konečně jsme se dostali do metody, ve které si budeme moci ukázat nejrůznější možnosti, které nám nabízí okno debuggeru. Opusťme proto na chvíli krokování a podívejme se blíže na okno debuggeru (viz obr. 4.12). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 215 z 433 216 Myslíme objektově v jazyku Java 1.5 Obrázek 4.12 Okno debuggeru Pracovní plocha okna je rozdělena na pět oblastí, jejichž vzájemnou velikost můžete měnit uchopením a přesunutím jejich hranic pomocí myši, jak je ukázáno na obrázku. Vlákna Horní široká oblast je vyhrazena pro informace o spuštěných vláknech. Vlákny jsou přitom myšleny samostatné procesy, které vykonávají relativně nezávislé úlohy – např. jedno vlákno může kreslit obrázek zatímco druhé otevře dialogové okno a čeká na vaše vyjádření k tomu, co páchá vlákno první. Pořadí volání Levá oblast popisuje situaci na tzv. zásobníku návratových adres. Zde můžeme zjistit, kdo koho volal a v jakém místě, přičemž naposledy volaná metoda je zobrazena nahoře. Z výpisu na obr. 4.12 můžeme poznat, že jsme v konstruktoru (symbol <init> označuje konstruktor) třídy Start, který byl volán jiným konstruktorem, ten zase @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 216 z 433 Kapitola 4: Dotváříme vlastní třídu 217 jiným a ten byl volán metodou setUp třídy Test_04. Další metody jsou již systémové (mají na starosti správné spuštění testu), a proto je zde nebudu dále rozebírat. Klepnutím na kteroukoliv z položek tohoto seznamu otevřete editor se zdrojovým kódem třídy, v níž se metoda nachází (musí to být třída z vašeho projektu). Editor zobrazí právě vykonávaný řádek. Ten bude pro vyšší názornost podšeděný a označený šipkou. Tak můžete relativně zjistit, kudy se program dostal k místu, kde jste jej zastavili. Klepnete-li náhodou na položku, jejíž zdrojový kód nemá BlueJ k dispozici, otevře dialogové okno, v němž vás na tuto skutečnost upozorní. Obrázek 4.13 Reakce prostředí na skutečnost, že chcete krokovat třídu, od níž nemáte zdrojový kód. Atributy třídy Vpravo nahoře je zóna vyhrazená pro atributy třídy. Jedná se přitom o atributy té třídy, ve které se nachází aktivní položka z oblasti Pořadí volání. Ověřte si, že když klepnete na položku Test_04.setUp, objeví se zde místo atributů třídy Strom atributy třídy Test_04. Atributy instancí Pod oblastí pro atributy třídy je oblast vyhrazená pro atributy instancí. Zase zde najdete pouze atributy té instance, jejíž metodu máte zrovna označenou v Pořadí volání. Lokální proměnné Jak nadpis napovídá, v třetím panelu najdete lokální proměnné metody. Stejně jako v předchozích dvou polích se i zde jedná o metodu zvýrazněnou v panelu Pořadí volání. Zde bych jenom připomenul, že mezi lokální proměnné se počítají i parametry. Ostatně v tuto chvíli v poli ani jiné lokální proměnné neuvidíte, protože program si své lokální proměnné vytváří až v okamžiku, kdy je opravdu potřebuje a @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 217 z 433 218 Myslíme objektově v jazyku Java 1.5 teprve tehdy se o nich dozví i debugger, který se vám snaží zobrazit jejich hodnoty. Můžete si to hned vyzkoušet. Když nyní stisknete tlačítko Krokovat, vytvoří se (a zobrazí v debuggeru) proměnná výškyKmene a přiřadí se jí spočtení hodnota – v našem případě 50. Při dalších krocích se postupně vytvoří proměnné výškaKoruny, šířkaKmene a posunKmene. Při krokování některých metod můžete být zaskočení tím, že vám debugger nezobrazí všechny lokální proměnné. Nejčastější příčinou tohoto nedorozumění je to, že debugger zobrazuje pouze definované proměnné, tj. proměnné, které již mají vyhrazenu paměť a přiřazenou počáteční hodnotu. Pokud tedy lokální proměnnou pouze deklarujete, tj. pouze uvedete, jak se bude proměnná jmenovat a jakého bude typu, debugger ji ignoruje. Všimne si jí až poté, co jí přiřadíte nějakou hodnotu. Atributy a proměnné objektových typů Nyní nás ve zdrojovém textu konstruktoru stromu čeká vytvoření elipsy a přiřazení odkazu na ni do proměnné koruna. Podíváte-li se mezi atributy instance, zjistíte, že jak koruna tak kmen mají indikovánu hodnotu null, která říká, že v danou chvíli neukazují nikam (vysvětlovali jsme si to v oddíle null na straně 148). Popojeďme nyní dále a nechme program učinit další krok. Na plátně se objeví zelený kruh představující korunu budoucího stromu a debugger nám ve svém okně prozradí, že atribut koruna obsahuje odkaz na objekt (viz obr. 4.14). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 218 z 433 Kapitola 4: Dotváříme vlastní třídu 219 Obrázek 4.14 U objektových instancí a proměnných debugger pouze oznámí, že někam ukazují Víc se z okna debuggeru nedozvíme. Musíme se smířit s tím, že nyní víme, že atribut koruna na rozdíl od atributu kmen někam ukazuje. Naštěstí u toho nemusí zůstat. Když pěkně poprosíme, je nám debugger ochoten prozradit víc. Poproste poklepáním na příslušný řádek a debugger otevře okno prohlížeče instancí, který vám prozradí hodnoty všech atributů primitivních typů (viz obr. 4.15). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 219 z 433 220 Myslíme objektově v jazyku Java 1.5 Obrázek 4.15 Poklepáním otevřeme okno prohlížeče pro danou instanci Pravda, atribut barva je opět objektového typu, takže se znovu dozvíme pouze to, že někam ukazuje, ale poklepáním na něj otevřeme další okno prohlížeče, v němž se můžeme podívat barvě na zoubek. Už nezastavuj – ruším zarážky Takto bychom mohli pokračovat až do konce. Krokování větších aplikací by ale bylo příliš dlouhé. Naštěstí tu máme ale tlačítko Pokračovat, které znovu rozběhne program bez krokování a jak jsme si již řekli, zastaví se až na další zarážce a není-li už v programu další zarážka, program v klidu skončí. Problémem by mohlo být, kdyby byl program napsán tak, že by se na dané zarážce měl zastavit ještě stokrát. Pomoc je ale jednoduchá: klepnutím na ikonu zarážky tuto zarážku zrušíme a program může v klidu doběhnout. Zarážky můžeme nejenom rušit, ale také zřizovat nové. Zjistíte-li, že o kousek dál je místo, na němž byste rádi zastavili, ale nechce se vám k němu „prokrokovávat“, můžete na příslušný řádek umístit zarážku a pak říci programu, aby pokračoval. Předčasný konec programu Někdy se stane, že při krokování programu objevíte chybu a víte, že už nemá smysl pokračovat dále, protože musíte tuto chybu nejprve opravit. Snadná pomoc: stiskněte tlačítko Ukončit a program předčasně ukončete. Vedle tlačítka v okně debuggeru nabízí BlueJ ještě jeden způsob předčasného zastavení programu: je jím známá možnost restartu virtuálního stroje. Když pro@Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 220 z 433 Kapitola 4: Dotváříme vlastní třídu 221 gram běží, tak indikátor běhu programu změní své vybarvení ze šedobílého na červenobílé. Klepnete-li na něj pravým tlačítkem a zadáte-li Restartovat VM, program se předčasně ukončí. Pozastavení běžícího programu Jsou chvíle, kdy se vám zdá, že program pracuje nějak podezřele dlouho, aniž by byly zřejmé jakékoliv výsledky jeho práce. Důvody mohou být dva: Program někde otevřel dialogové okno a čeká, až mu odpovíte. Zejména připráci na počátečních projektech bude asi tento případ mnohem častější. Pokud okno nevidíte, musíte minimalizovat nebo alespoň odsunout všechna ostatní okna, abyste se probojovali k oknu, které na vás čeká a mohli stisknout požadované tlačítko. Program opravdu někde něco dělá a vy byste chtěli vědět, jestli jste jej tak pomalý naprogramovali anebo jestli někde zabloudil. V obou případech budou v takovéto situaci v okně debuggeru zhasnutá všechna tři střední tlačítka. Určitě bude aktivní tlačítko Ukončit, kterým můžete celý program předčasně ukončit. Mohlo by být aktivní také tlačítko Zastavit, ale museli byste se před tím alespoň jednou zastavit na nějaké zarážce. Dokud se nezastavíte na zarážce, debugger se tváří, že o tlačítku Zastavit neví. Dáte-li si ale na nějaké dostatečně počáteční místo programu zarážku, pak tlačítko Zastavit ožije pokaždé, když program běží. Potřebujete-li se proto někdy uprostřed běhu programu dozvědět, kde se zrovna program nachází, stisknete je, okno debuggeru se zaplní stejně, jako při zastávce na zarážce a vy můžete začít klepat na jednotlivé položky v Pořadí volání a zjišťovat aktuální stav jednotlivých atributů a lokálních proměnných. Když zjistíte, co jste potřebovali zjistit, stisknete tlačítko Pokračovat a program se zase rozběhne dál. Krokování konstruktorů Při krokování konstruktorů musíte mít na paměti, že do konstrukce objektu patří i inicializace atributů definovaná v jejich deklaracích, která se spouští před spuštěním vlastního těla konstruktoru. Chcete-li proto krokovat tuto inicializaci, musíte vložit zarážku na příslušný inicializační příkaz. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 221 z 433 222 Myslíme objektově v jazyku Java 1.5 4.11 Hodnotové a referenční typy V kapitole Konstanty a literály na straně 143 jsem hovořil o tom, že konstanty primitivních typů bývají často deklarovány jako veřejné. U konstant objektových typů je to složitější, protože tyto konstanty nemusejí být tak úplně konstantní, jako je tomu u typů primitivních. Rozdíl spočívá v tom, že v konstantách primitivních typů je uložena přímo hodnota, kdežto v konstantách objektových typů je pouze odkaz na instanci. Když celočíselné konstantě přiřadíme počáteční hodnotu, víme, že tuto hodnotu bude mít po věky věků (přesněji dokud program nevypneme). U konstant objektových typů se můžeme spolehnout pouze na to, že se nikdy nezmění uložený odkaz, tj. že konstanta bude odkazovat stále na tu samou instanci. To, že odkaz na instanci je uložen v konstantě však ještě nic neříká o tom, co se bude dít s vlastní instancí. To záleží na tom, jak je daná třída definována. V této souvislosti se rozlišují tzv. hodnotové a referenční objektové typy. Podívejme se na vlastnosti každé skupiny zvlášť. Hodnotové typy Jak název napovídá, hodnotové typy slouží k tomu, aby uchovávali nějakou hodnotu. Máme-li dvě instance hodnotových typů, můžeme zjišťovat, obsahují-li stejnou hodnotu obdobně, jako se na to můžeme ptát u dvou proměnných primitivních datových typů. Z tříd, s nimiž jsme se doposud setkali, jsou hodnotovými typy String, Barva, Směr, Pozice a Rozsah. U jejich instancí má smysl se pídit po shodnosti hodnot, tj. obsahují-li dvě instance třídy String stejný řetězec či označují-li dvě instance třídy Pozice stejné místo na plátně. Chování instancí hodnotových tříd a z toho plynoucí jejich „konstantová spolehlivost“ se ale liší. Hodnotové třídy můžeme rozdělit do dvou skupin: Neměnné (anglicky immutable) třídy nám nenabízejí žádnou možnost, jak změnit hodnotu uchovávanou v dané instanci. Konstanty neměnných hodnotových typů se proto chovají naprosto stejně, jako hodnoty primitivních datových typů. Jakmile do nich uložíme odkaz na nějakou hodnotu, můžeme se spolehnout na to, že už budou vždycky odkazovat na tuto hodnotu. Konstanty těchto typů můžeme proto s klidným svědomím deklarovat jako veřejné. Z dosud používaných tříd sem patří třídy String, Barva a Směr. Proměnné (anglicky mutable) třídy nám neměnnost uchovávaných hodnot nezaručí. Hodnota uchovávaná v jejich instanci se naopak může kdykoliv @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 222 z 433 Kapitola 4: Dotváříme vlastní třídu 223 změnit. Odkazy na instance takovýchto tříd do konstant nepatří a už vůbec ne do veřejných konstant. Do této skupiny bychom zařadili třídy Pozice, Rozsah. Referenční datové typy U referenčních datových typů se po žádné hodnotě nepídíme. Mohli bychom říci, že jejich hodnotou je instance sama. Z tříd, s nimiž jsme doposud pracovali, bychom do této skupiny mohli zařadit všechny grafické objekty. Např. to, že dva obdélníky mají stejnou barvu, jsou stejně veliké a na stejných souřadnicích nám nepřipadá jako důvod k tomu, abychom je považovali za shodné. Jsou to pro nás stále dva různé obdélníky, které se pouze v daný okamžik překrývají. V řadě případů nás u instancí referenčních datových typů zajímá pouze to, o kterou instanci se jedná, a nijak nám nevadí, že její vlastnosti v průběhu času mění někdo cizí (např. že tvar posouvá nebo mění jeho rozměr). V takovýchto případech bývá výhodné deklarovat daný atribut jako veřejnou konstantu. Ta nám zaručí, že nám nikdo nenahradí daný objekt jiným, avšak jinak přístup ostatních tříd k tomuto objektu nijak neomezuje. Program demonstrující rozdíl Abychom nezůstali jenom u teorie, ukážu vám jednoduchý prográmek demonstrující rozdíly mezi hodnotovými a referenčními datovými typy. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 public static void testík() { //Definujeme tři proměnné hodnotového typu String String hodnota_1 = "První"; String hodnota_2 = "Druhý"; //Druhá a třetí proměnná bude odkazovat na stejný objekt String hodnota_3 = hodnota_2; //Definujeme tři proměnné referenčního typu Strom Strom odkaz_1 = new Strom(); Strom odkaz_2 = new Strom(); //Druhá a třetí proměnná bude odkazovat na stejný objekt Strom odkaz_3 = odkaz_2; System.out.println ( "Stav před úpravou:\n" + hodnota_1 + "\n" + hodnota_2 + "\n" + hodnota_3 + "\n\n" + odkaz_1 + "\n" + odkaz_2 + "\n" + odkaz_3 ); //Změním hodnotu objektu odkazovaného druhou proměnnou //U hodnotových typů bude objekt nahrazen jiným hodnota_2 += "+doplněk"; @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 223 z 433 224 23 24 25 26 27 28 29 30 Myslíme objektově v jazyku Java 1.5 //U odkazovaných objektů se změní pouze stav objektu odkaz_2.posunVpravo(); } System.out.println ( "\n\nStav po úpravě:\n" + hodnota_1 + "\n" + hodnota_2 + "\n" + hodnota_3 + "\n\n" + odkaz_1 + "\n" + odkaz_2 + "\n" + odkaz_3 ); Metodu testik() najdete ve třídě HodnotyAOdkazy, kterou si můžete zkopírovat z projektu 03_Třídy_Z. Po jejím spuštění se v okně terminálu vypíše text, jenž si můžete prohlédnout na obr. 4.16. Obrázek 4.16 Výsledek práce metody testík() třídy HodnotyAOdkazy Před úpravou odkazuje proměnná hodnota_2 na stejný objekt jako proměnná hodnota_3 – tímto objektem je řetězec s textem "Druhý". Obdobně proměnná odkaz_2 odkazuje na stejný objekt jako proměnná odkaz_3 – obě ukazují na instanci stromu s názvem Strom_2. Po provedených operacích na řádcích 22 a 25 se však tento stav změní. Obě „stromové“ proměnné budou stále ukazovat na stejný objekt. Tím, že jsme upravili objekt, na který ukazuje jedna z nich, jsme logicky upravili i objekt, na nějž ukazuje ta druhá (aby ne, když ukazují na tentýž). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 224 z 433 Kapitola 4: Dotváříme vlastní třídu 225 U řetězcových proměnných, tj. proměnných typu String, je to jiné. Jakýkoliv pokus o úpravu objektu neměnného hodnotového datového typu vede k tomu, že se dané proměnné přiřadí odkaz na jiný objekt. Po úpravě proto ukazuje každá proměnná na jiný objekt. Proměnná hodnota_3 ukazuje pořád na původní řetězec, v němž je uložen text "Druhý". Protože opomenutí popsaného chování řetězců patří k velmi častým začátečnickým chybám, tak vám to zopakuji ještě jednou: Datový typ String je neměnný hodnotový datový typ a jakýkoliv pokus o úpravu hodnoty jeho instance vede k vytvoření nové instance. 4.12 Projekt Zlomky Tak už jsme si toho napovídali pěknou řádku a je nejvyšší čas ukázat si, že programování není jenom o malování obrázků na plátno. Mám pro vás proto samostatnou úlohu (budete si ale muset vzpomenout na hodiny matematiky na základní škole): Definujte třídu Zlomek, jejíž instance budou mít dva soukromé atributy: čitatel a jmenovatel. Definujte ve třídě sadu metod řešících jednoduché operace zlomkové aritmetiky: sčítání, odčítání, násobení a dělení zlomků, resp. zlomku a čísla. Třída by měla být neměnným hodnotovým typem. Proto nesmějí metody počítající výsledky aritmetických operací vracet upravenou hodnotu své instance či parametru, ale musí vždy vytvořit novou instanci a vrátit odkaz na ni. Jedině tak můžete zaručit, že se budou zlomky ve výrazech chovat stejně jako čísla. Ve svých programech využijte třídu Funkce, konkrétně její statickou metodu nsn(int,int), která vrací nejmenší společný násobek svých parametrů a statickou metodu nsd(int,int), která vrací největší společný dělitel svých parametrů. Abyste nemuseli tolik přemýšlet a psát, je pro vás v projektu 04_Zlomky připravena třída Zlomek s prázdnými těly metod a třída TestZlomek s příslušnými testy. Vaším úkolem tedy je upravit těla metod třídy Zlomek tak, aby všechny připravené testy prošly. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 225 z 433 226 Myslíme objektově v jazyku Java 1.5 Pro ty největší lenochy je v projektu definována i dvojice tříd ZlomekRUP a TestZlomekRUP, které obsahují vzorové řešení a jeho testy. Jestli se ale chcete opravdu něco naučit, pokuste se nejprve vše vyřešit sami a tato řešení použijte pouze pro kontrolu. Pro radu se na ně obracejte opravdu pouze ve stavu nejvyššího zoufalství. 4.13 Shrnutí – co jsme se naučili Třída P obsahuje statické metody zpráva, zadej a souhlas, které umožňují jednoduché zadávání výstupních zpráv a požadavků na vstupní hodnoty prostřednictvím dialogových oken. Operátor je znak nebo skupina znaků naznačujících, že se v daném místě má provést nějaká operace. V jazyku Java dělíme operátory podle tzv. arity, která označuje, kolik operandů daný operátor vyžaduje. V Javě rozeznáváme operátory unární (s jedním operandem – např. -5), binární (se dvěma operandy – např. 5 + 4) a ternární se třemi operandy (ten jsme ještě neprobírali). Některé operátory mají přednost před ostatními a vyhodnocují se proto dřív (obdobně jako se v matematice nejprve násobí a pak teprve sčítá). Pořadí vyhodnocování lze ale vždy upravit pomocí kulatých závorek. Je-li jeden z operandů operátoru sčítání textový řetězec (String), převede se na řetězec i druhý operand a výsledkem operace je řetězec vzniklý spojením obou řetězců. Výsledek operace dělení záleží na typu operandů. Dělíme-li celé číslo celým číslem, získáme celou část podílu. Je-li některý z operandů reálný, získáme „přesný“ podíl. Operátor % vrací zbytek po dělení a je aplikovatelný i na reálná čísla. Potřebujeme-li změnit typ výsledku operace, můžeme použít operátor přetypování. Chceme-li identifikovat instanci nějakým jejím „rodným číslem“, můžeme v atributu třídy načítat počet vytvořených instancí a do konstantního atributu instance uložit vždy při vzniku instance její pořadí. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 226 z 433 Kapitola 4: Dotváříme vlastní třídu 227 Pro zvýšení hodnoty číselného atributu o jedničku můžeme použít inkrementační operátor ++, pro její snížení dekrementační operátor – –. Při umístění tohoto operátoru před identifikátor se nejprve provede operace a výsledná hodnota se použije ve výrazu, při umístění za identifikátor se nejprve použije původní hodnota a teprve pak se upraví. Pro tisk zpráv na standardní výstup používáme metody print a println objektu out, jenž je veřejným statickým atributem třídy System. Texty posílané na standardní výstup zobrazuje BlueJ v okně terminálu. Pošleme-li na standardní výstup znak pro odstránkování (ʹ\fʹ), BlueJ okno terminálu smaže. Třída může definovat veřejnou metodu toString, která se zavolá vždy, když je potřeba převést příslušnou instanci na řetězec. Při vytváření nových tříd je výhodné použít šablonu standardní třídy, která má připravené definice často používaných atributů a metod a sadu řádkových komentářů specifikujících doporučené pořadí jednotlivých definic. V testovací třídě chápe systém metody začínající předponou test jako testy, které je schopen na požádání spustit. Testy můžeme vytvářet nejen automaticky, ale těla testovacích metod můžeme upravovat jako jakékoliv jiné programy. Před spuštěním každého testu se nejprve provede metoda setUp(), která vytvoří tzv. přípravek (fixture), což je výchozí sada objektů, se kterou testy pracují. Po ukončení každého testu se provede metoda tearOff(), která má za úkol po vykonaném testu „uklidit“. Program je možno v kterémkoliv místě zastavit a podívat se na hodnoty jednotlivých atributů a lokálních proměnných. Slouží k tomu speciální zarážky (breakpoint), které je možno umístit do záhlaví řádku programu. Když systém při vykonávání programu narazí na zarážku, otevře okno debuggeru, jež umožní bližší analýzu programu. Debugger umožňuje program krokovat, a to jak po jednotlivých příkazech metody, v níž se zrovna nachází, tak i vnořením se do útrob metody, která se má právě vykonat. BlueJ nabízí i možnost, jak předčasně zastavit běžící program. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 227 z 433 228 Myslíme objektově v jazyku Java 1.5 Objektové datové typy můžeme rozdělit na hodnotové a referenční. Hodnotové datové typy můžeme ještě rozdělit na proměnné a neměnné. Neměnné datové typy můžeme používat obdobně jako typy primitivní. Můžeme např. bezpečně deklarovat veřejné konstanty těchto typů. Deklarace veřejných konstant proměnných hodnotových typů je velice nebezpečná. Nové termíny @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 228 z 433 Kapitola 5: Návrhové vzory 5. 229 Návrhové vzory Kapitola 5 Návrhové vzory Opravit ☯ Co se v kapitole naučíme V této kapitole si vysvětlíme, co jsou to návrhové vzory a jaký význam mají při tvorbě programů. Vzápětí si vysvětlíme princip, základní vlastnosti a typické použití prvních vzorů: přepravky a jedináčka. V závěru kapitoly si pak ukážeme, jak je možno definovat výčtové typy. Jednou z důležitých zásad produktivních programátorů je „nevynalézat již vynalezené“. Celé objektově orientované programování je „vyladěno“ k tomu, aby při řešení nových úloh programátor nemusel znovu vynalézat nové třídy, ale aby místo toho mohl znovu použít třídy, které naprogramoval někdy dříve. Snaha o zefektivnění vývoje programů a „nevymýšlení“ dříve vymyšleného jde ještě dále. V posledních letech jsou stále populárnější tzv. návrhové vzory (anglicky design patterns), které bychom mohli charakterizovat jako návody na řešení některých typických, často se vyskytujících úloh. Oblibu návrhových vzorů rozpoutalo v roce 1995 vydání knihy Design Patterns1, která se stala velice rychle biblí všech objektově orientovaných programátorů a povinnou výbavou jejich knihovničky. Kniha obsahuje 23 základních návrhových vzorů všeobecného použití. V počítačové literatuře se autoři na tyto vzory často odkazují, takže se zkušený programátor bez jejich znalosti neobejde2. 1 2 Gamma E., Helm R., Johnson R., Vlissides J.: Design Patterns: Elements of Reusable Object-Oriented Software, Addison-Wesley, 1955. Český překlad vydalo v roce 2003 nakladatelství Grada pod názvem Návrh programů pomocí vzorů – Stavební kameny objektově orientovaných programů (ISBN 80-247-0302-5). Autoři této knihy bývají v literatuře často označování jako „gang čtyř“ (anglicky gang of four – ve zkratce GoF). Narazíte-li proto někde na odkaz GoF nebo gang čtyř, jedná se určitě @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 229 z 433 230 Myslíme objektově v jazyku Java 1.5 Od té doby byla publikována řada dalších návrhových vzorů, především pak z oblasti vývoje rozsáhlých aplikací běžících na několika počítačích. Nicméně výše zmíněná kniha je všude citována jako základní zdroj prvotních informací z této oblasti. Převážná většina učebnic programování je ve skutečnosti učebnicemi syntaxe a základního použití vybraného programovacího jazyka, a proto tyto učebnice žádné zmínky o návrhových vzorech neobsahují (případně jen okrajově) a počítají s tím, že se s nimi čtenář ve své další praxi seznámí. Protože se domnívám, že znalost nejdůležitějších návrhových vzorů patří k základním znalostem objektového programátora, seznámím vás postupně alespoň s těmi jednoduššími návrhovými vzory. Ke vzorům popsaným ve výše zmiňované knize GoF přidám i některé další, které sice ve zmiňované knize nejsou (asi je autoři považovali za příliš triviální), avšak které se vám budou hodit. 5.2 Přepravka (Messenger) Hned první ze vzorů, s nimiž vás chci seznámit, v knize GoF není. Tento vzor je v anglické literatuře označován jako Messenger1, což bychom mohli přeložit jako posel nebo kurýr, avšak já mu říkám přepravka. Jeho účelem je totiž umožnit pracovat s několika hodnotami jako s hodnotou jedinou, a umožnit tak snadné přenášení této skupiny hodnot mezi jednotlivými instancemi a jejich metodami. Můžete si představit, že všechny hodnoty naskládáte do přepravky, ve které je předáte tomu, kdo po nich touží. Místo řady jednotlivých hodnot, k nimž se musíte obracet jednotlivě, tak můžete použít jedinou přepravku, v níž máte vše naskládané. Je to obdobné, jako když jdete nakoupit. Také při větším nákupu nenesete všechny věci v náručí, ale pořídíte si na ně raději tašku a pro větší nákupy dokonce pojedete autem. Na rozdíl od tašky, kam pokaždé nahážete něco jiného, bývají přepravky určeny pro předem definovanou sadu hodnot – můžete si je představit např. jako kufřík na nářadí, kde má každý druh nářadí připraven vlastní úchyt. 1 o odkaz na výše uvedenou knihu. Tuto zkratku budu v odkazech na knihu používat v dalším textu i já. Viz např. učebnice Thinking in Patterns stáhnutelná zdarma z adresy www.bruceeckel.cz. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 230 z 433 Kapitola 5: Návrhové vzory 231 Třída, jejíž instance mají sloužit jako přepravky předem známé sady několika hodnot, by měla mít následující vlastnosti: Pro každou přenášenou hodnotu bude definován atribut, do kterého se pak tato hodnota uloží. Protože účelem třídy není hodnoty atributů svých instancí schovat, ale pouze je shromáždit, aby se snadněji přenášely z místa na místo, deklarují se jejich atributy většinou jako veřejné. To s sebou ovšem nese povinnost dávat pozor na to, abychom tyto atributy náhodou zvenku nepřípustným způsobem neovlivnili. S prvními třídami, které jsou implementací tohoto vzoru, jste se seznámili již v pasáži Kvalifikace atributů na straně 134. Instance tříd Pozice a Rozměr nebyly ničím jiným než takovými přepravkami. V této kapitole k nim přidáme ještě třídu Oblast, jejíž instance uchovávají informace o pozici a rozměru příslušné oblasti. Správná přepravka však není pouze schránkou na předávané hodnoty, ale bývá zároveň doplněna přístupovými metodami ke svým atributům. Její atributy sice jsou veřejné, ale definicí přístupových metod danou třídu sjednotíme s běžnými zvyklostmi, takže kdo bude chtít, bude moci přistupovat k jejím atributům stejně, jako by přistupoval k soukromým atributům standardních tříd. Vedle metod, které přímo nastavují hodnoty jednotlivých atributů, bývají třídy sloužící jako přepravky dovybaveny metodami, které do jejich atributů kopírují hodnoty atributů instance předávané jako parametr. A při té příležitosti jsou také řady jejich konstruktorů rozšířeny o tzv. kopírovací konstruktor, který vytváří novou instanci jako kopii instance předané jak parametr. O tom, co by měla přepravka obsahovat, jsem se již navyprávěl dost. Pokuste se nyní definovat třídu Oblast. Nezapomeňte jí definovat kopírovací konstruktor a přístupové metody. Umožněte v nich předávat jako parametry nejenom čísla a instance třídy Oblast, ale i instance tříd Pozice a Rozměr. Tak co, hotovo? Svůj program si můžete porovnat se vzorovým řešením v projektu 05_vzory. V něm si pak můžete prohlédnout i plné definice tříd Pozice a Rozměr. Abychom mohli definované přepravky plně využít, je vhodné rozšířit množinu konstruktorů a metod našich grafických tříd o verze, které budou přebírat tyto přepravky jako své parametry, a o verze přístupových metod, které budou takovéto přepravky přebírat a vracet. V projektu 05_vzory jsem základní grafické třídy o tyto konstruktory a metody doplnil. V následujícím výpisu si můžete prohlédnout, jak lze jednoduše doplnit @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 231 z 433 232 Myslíme objektově v jazyku Java 1.5 třídu Strom o metody pro čtení a nastavování pozice instance a o metody pro zjišťování jejího rozměru a zabrané plochy. Asi vás napadlo, proč chybějí metody pro nastavování plochy. Je to proto, že jsme ještě nedefinovali metodu setRozměr(int,int), protože od chvíle, kdy jsme zavedli možnost zadat poměr výšky, resp. šířky kmene a celého stromu, se stala její definice trochu složitější a vyžaduje změny v doposud definované části třídy. Nechal jsem si ji proto do následující kapitoly, kde si na ní ukážeme, jak při takových „vynucených“ změnách programu postupovat. 1 // ... Předchozí část definice třídy 2 3 //== KONSTRUKTORY A TOVÁRNÍ METODY ============================================ 4 /************************************************************************** 5 6 * Vytvoří novou instanci se zadanou polohou a rozměry 7 * a implicitní barvou. * 8 9 * @param počátek Pozice počátku instance * @param rozměr Rozměr instance 10 11 */ public Strom(Pozice počátek, Rozměr rozměr) 12 13 { 14 this( počátek.x, počátek.y, rozměr.šířka, rozměr.výška ); } 15 16 17 18 /************************************************************************** * Vytvoří novou instanci vyplňující zadanou oblast 19 20 * a mající implicitní barvu. 21 * * @param oblast Oblast definující pozici a rozměr instance 22 23 */ public Strom(Oblast oblast) 24 25 { this( oblast.x, oblast.y, oblast.šířka, oblast.výška ); 26 27 } 28 29 30 //== PŘÍSTUPOVÉ METODY ATRIBUTU INSTANCÍ ====================================== 31 32 /************************************************************************** 33 * Vrátí instanci třídy Pozice s pozicí instance. 34 * 35 * @return Pozice s pozicí instance. 36 */ 37 public Pozice getPozice() 38 { 39 return new Pozice( xPos, yPos ); 40 } 41 42 43 /************************************************************************** 44 * Nastaví novou pozici počátku instance. * 45 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 232 z 433 Kapitola 5: Návrhové vzory 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 // 233 * @param pozice Nová pozice instance */ public void setPozice(Pozice pozice) { setPozice( pozice.x, pozice.y ); } /************************************************************************** * Vrátí instanci třídy Rozměr s rozměry instance. * * @return Rozměr s rozměry instance. */ public Rozměr getRozměr() { return new Rozměr( šířka, výška ); } /************************************************************************** * Vrátí instanci třídy Oblast s informacemi o pozici a rozměrech instance. * * @return Oblast s informacemi o pozici a rozměre instance. */ public Oblast getOblast() { return new Oblast( xPos, yPos, šířka, výška ); } ... Následující část definice třídy Jak vidíte, začlenění práce s přepravkami do programu je opravdu jednoduché. Od této chvíle může jeden obrazec požádat např. o instanci pozice a pak tuto instanci předat dalším obrazcům tak, aby si všechny mohly dát na daném místě „rande“. 5.3 Jedináček (Singleton) Některé třídy potřebujeme definovat tak, aby uživatel nemohl svobodně ovlivňovat počet jejich instancí. Typickým příkladem je např. třída Plátno, o které jsme si již několikrát řekli, že trvá na tom, aby její instance byla jedináček. Chceme-li totiž zabezpečit, aby se všechny grafické obrazce malovaly na jedno a to samé plátno, musíme zařídit, aby další plátno nebylo možné žádným způsobem vytvořit, protože jinak bychom museli definovat nějaké pravidlo, podle nějž se budou instance rozhodovat, ke kterému plátu patří. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 233 z 433 234 Myslíme objektově v jazyku Java 1.5 Vytvoření třídy, která bude mít jedinou instanci, popisuje návrhový vzor označovaný v anglické literatuře jako singleton, což bychom mohli do češtiny přeložit jako jedináček. Třída, jejíž instance má být jedináček (případně má mít předem definovaný počet instancí, ale o tom až za chvíli), musí splňovat tři podmínky: Definovat konstruktor jako soukromý, aby jej nemohl nikdo jiný zavolat vytvořit tak další instanci. Deklarovat odkaz na instanci, která bude jedináčkem, jako atribut třídy, jenž bude hned v deklaraci inicializován zavoláním konstruktoru (deklarace tak bude zároveň definicí). (Časem se naučíme ještě jiné způsoby definice jedináčka.) Definovat metodu třídy (statickou metodu), která na požádání poskytne odkaz na tohoto jedináčka. Základní rozdíl mezi konstruktorem a metodou vracející odkaz na instanci je v tom, že konstruktor musí po svém zavolání vytvořit novou instanci (musí ji zkonstruovat – proto se tak také jmenuje), kdežto obyčejná metoda se může svobodně rozhodnout, zda vytvoří instanci novou nebo vrátí odkaz na instanci existující. Teoreticky by bylo možné se zavedení takovéto metody vyhnout a definovat jedináčka jako veřejnou statickou konstantu dané třídy, jenže tím by konstruktér třídy zbytečně odhaloval podrobnosti o způsobu implementace a porušoval by tak pravidlo o jejím zapouzdření. Těžko by se pak např. mohl v budoucnu rozhodnout změnit definici třídy tak, aby místo jediné instance využívala služeb několik instancí. Jakmile se totiž něco jednou objeví v rozhraní třídy (tj. mezi jejími veřejnými členy), kterou začnou používat jiné třídy, to už tam musí zůstat. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 234 z 433 Kapitola 5: Návrhové vzory 235 Abyste si tvorbu jedináčka vyzkoušeli, nadefinujte třídu ČernáDíra, která definuje ve středu plátna malý černá kruh. Metodu, která bude vracet odkaz na jedináčka, nazvěte getČernáDíra. Pocvičte si navíc svoji logiku a pokuste se definovat sérii metod spolkni(X), jejichž parametry budou postupně všechny tři základní tvary, tj. Elipsa, Obdélník a Trojúhelník. Tato metoda na třikrát (tj. na tři posuny s drobným oddechem mezi nimi) přitáhne objekt, který převezme jako parametr, a když bude střed objektu nad středem černé díry, tak jej na třikrát spolkne, tj. třikrát jej vždy zmenší a po třetím zmenšení jej odmaže. Mezi jednotlivými přesuny, resp. zmenšeními počkejte vždy 0,5 sekundy prostřednictvím zavolání metody P.čekej(milisekund). Tentokrát vás výjimečně nebudu nutit k tomu, abyste třídu definovali sami. Bude-li se vám zdát, že jste způsob implementace jedináčka zcela nepochopili, podívejte se do následujícího vzorového řešení. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 /******************************************************************************* * Třída ČernáDíra slouží k ukázce definice třídy, * jejíž instance má zůstat jedináčkem. * * @author Rudolf Pecinovský * @version 2.01, duben 2004 */ public class ČernáDíra { //== KONSTANTNÍ ATRIBUTY TŘÍDY ================================================= /** Průměr kruhu tvořícího černou díru. */ private static final int PRŮMĚR = 10; private static final int POLOMĚR = PRŮMĚR / 2; /** Definuje jedináčka jako konstnatní atribut třídy a hned mu také * přiřazuje počáteční hodnotu. */ private static final ČernáDíra jedináček = new ČernáDíra( PRŮMĚR ); //== KONSTANTNÍ ATRIBUTY INSTANCÍ ============================================== /** Odkaz na kruh, který na plátně představuje danou díru. */ private final Elipsa díra; //== PŘÍSTUPOVÉ METODY ATRIBUTŮ TŘÍDY ========================================== //############################################################################## //== KONSTRUKTORY A METODY VRACEJÍCÍ INSTANCE VLASTNÍ TŘÍDY ==================== /*************************************************************************** * Metoda vracející odkaz na jedináčka. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 235 z 433 236 Myslíme objektově v jazyku Java 1.5 */ 33 public ČernáDíra getČernáDíra() 34 { 35 return jedináček; 36 } 37 38 39 /*************************************************************************** 40 * Implicitní konstruktor třídy ČernáDíra 41 */ 42 private ČernáDíra( int průměr ) 43 { 44 Plátno plátno = Plátno.getPlátno(); 45 int x = plátno.getŠířka() / 2 - POLOMĚR; 46 int y = plátno.getVýška() / 2 - POLOMĚR; 47 díra = new Elipsa( x, y, PRŮMĚR, PRŮMĚR, Barva.ČERNÁ ); 48 } 49 50 51 52 //== NOVĚ ZAVEDENÉ METODY INSTANCÍ ============================================= 53 /*************************************************************************** 54 * Přesune zadanou instanci nad sebe a vcucne ji, tj. zmenší ji postupně 55 * až na nulovou velikost. Přesun i spolknutí provede vždy ve třech krocích. 56 * 57 * @param elipsa Polykaná elipsa. 58 */ 59 public void spolkni( Elipsa elipsa ) 60 { 61 int xe = elipsa.getX(); 62 int ye = elipsa.getY(); 63 int se = elipsa.getŠířka(); 64 int ve = elipsa.getVýška(); 65 int xd = díra.getX() + POLOMĚR; 66 int yd = díra.getY() + POLOMĚR; 67 68 int dx = (xd - (xe + se/2)) / 3; 69 int dy = (yd - (ye + ve/2)) / 3; 70 71 elipsa.setPozice( xe = xe + dx, ye = ye + dy ); 72 P.čekej( 500 ); 73 74 elipsa.setPozice( xe = xe + dx, ye = ye + dy ); P.čekej( 500 ); 75 elipsa.setPozice( xe = xe + dx, ye = ye + dy ); 76 P.čekej( 500 ); 77 78 dx = se / 6; 79 80 dy = ve / 6; 81 elipsa.setPozice( xe += dx, ye += dy ); elipsa.setRozměr( se -= 2*dx, ve -= 2*dy ); 82 P.čekej( 500 ); 83 elipsa.setPozice( xe += dx, ye += dy ); 84 elipsa.setRozměr( se -= 2*dx, ve -= 2*dy ); 85 P.čekej( 500 ); 86 elipsa.setPozice( xe += dx, ye += dy ); 87 88 elipsa.setRozměr( se -= 2*dx, ve -= 2*dy ); díra.nakresli(); 89 P.čekej( 500 ); 90 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 236 z 433 Kapitola 5: Návrhové vzory 237 91 92 elipsa.smaž(); díra.nakresli(); 93 94 } 95 96 97 //== TESTY A METODA MAIN ======================================================= 98 99 /*************************************************************************** * Testovací metoda. 100 101 */ public static void test () 102 103 { Elipsa e = new Elipsa(); 104 105 jedináček.spolkni( e ); 106 e = new Elipsa( 200, 200, 100, 50 ); jedináček.spolkni( e ); 107 108 } 109 110 111 }//public class ČernáDíra 5.4 Výčtové typy 5.5 Shrnutí – co jsme se naučili Návrhové vzory jsou doporučená řešení často se vyskytujících úloh. Jejich hlavním účelem je zefektivnit práci návrháře, který tak nemusí vymýšlet řešení, ale může pouze aplikovat osvědčený vzor. Návrhový vzor Přepravka (Messenger) ukazuje, jak postupovat v případě, kdy potřebujeme pracovat s několika hodnotami jako s celkem. Podle něj definujeme třídu (přepravku), která bude mít pro každou z těchto hodnot vyhrazen atribut. Oproti běžným zvyklostem budou atributy přepravky deklarovány jako veřejné. Jedináček (singleton) je třída, která povoluje vytvoření pouze jediné instance. Odkaz na tuto instanci bude mít uložen v atributu třídy, který bude inicializován hned v deklaraci atributu zavoláním příslušného konstruktoru. Třída nesmí definovat veřejný konstruktor. Konstruktor musí být soukromý a k získání odkazu na jedináčka slouží zvláštní metoda. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 237 z 433 238 Myslíme objektově v jazyku Java 1.5 Výčtový datový typ použijeme v případě, kdy budeme potřebovat definovat konečnou sadu hodnot (příkladem jsou např. směry). Nové termíny @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 238 z 433 Část 2: Více tváří @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 239 z 433 240 6. Myslíme objektově v jazyku Java 1.5 Rozhraní Kapitola 6 Rozhraní ☯ Co se v kapitole naučíme V této kapitole se seznámíme s novou vlastností tříd a jejich instancí. Označujeme ji jako polymorfizmus (mnohotvárnost) a vyjadřujeme jí skutečnost, že objekty se mohou v různých situacích vydávat za instance různých typů. Seznámíme se se zvláštním druhem třídy nazývaném rozhraní a ukážeme si, v jakých situacích je výhodné rozhraní používat a co jeho zavedení našim programům přináší. Vysvětlíme si jak třídy rozhraní implementují a předvedeme si, že jedna třída může implementovat i několik rozhraní. Seznámíme se také s novým návrhovým vzorem Služebník. V závěru kapitoly si v závěrečném projektu vyzkoušíme aplikaci získaných dovedností na příkladu grafické simulace problémů z „negrafického“ světa – zkusíte si naprogramovat simulaci výtahu. S novým tématem otevřeme i nový projekt, kde některé třídy přibudou, jiné budou chybět a definice ostatních budou alespoň upravené. Projekt pro tuto kapitolu se jmenuje 06_Rozhraní_A a stejně jako u minulého projektu má i on svého partnera 06_Rozhraní_Z, který bude obsahovat třídy, jejichž definice v průběhu lekce upravíme či přidáme. Ukažme si projevy polymorfizmu na příkladu ze života: jdete-li do restaurace, obsluhuje vás číšník. Kdybychom takovouto situaci programovali, pak by v našem programu vystupovali (mimo jiné) dva objekty: vy jako instance třídy Zákazník a obsluhující jako instance třídy Číšník. I číšníci však mají občas volno a mohou si pak dojít do restaurace. Pak ale vystupují jako instance třídy Zákazník a obsluhuje je jiná instance třídy Číšník. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 240 z 433 Kapitola 6: Rozhraní 241 Obdobných situací bychom v životě našli celou řadu. V této kapitole si ukážeme její ekvivalent ve světě grafických objektů a pláten, na která se tyto objekty kreslí. 6.1 Kreslíme jinak V našich dosavadních příkladech jsme se smiřovali s tím, že přesouvající se tvary smazaly ve své původní pozici nejenom sebe, ale i části tvarů, které zasahovaly pod ně či na ně. Pokud jsme chtěli nějaký umazaný nebo dokonce smazaný tvar vidět, museli jsme jej explicitně požádat o to, aby se překreslil. Kreslené objekty jeden o druhém nevěděly a ani plátno nevědělo nic o tom, co je na něm nakresleno. V této kapitole naše kreslící možnosti trochu vylepšíme. Opustíme třídu Plátno, kterou jsme používali doposud, a přestaneme říkat našim obrázkům, aby se nakreslily. Místo toho začneme používat třídu AktivníPlátno, která se o nakreslení našich obrázků na plátno postará sama. tj. sama je ve vhodnou chvíli požádá, aby se nakreslili. Třída AktivníPlátno obrací celou naši dosavadní filozofii kreslení obrázků naruby. Doposud jsme to dělali tak, že když jsme chtěli objekt nakreslit, požádali jsme třídu Plátno o nějaké plátno, tomu jsme nastavili kreslící barvu a nařídili mu, aby náš objekt touto barvou nakreslilo (podívejte se, jak byly v třídách Elipsa, Obdélník či Trojúhelník definovány metody nakresli()). Třída AktivníPlátno na to však jde úplně jinak. Její instance není pouze pasivním objektem, jenž nám umožňuje kreslit na obrazovku, ale chová se spíše jako manažer, který dostane nějaké objekty do správy a dohlíží na to, aby byly všechny správně nakresleny. Ví, které objekty jsou na plátně, a vždy, když se dozví, že se něco změnilo, tak zařídí, aby obrázek na plátně odpovídal skutečnosti. Jako správný manažer ale instance třídy AktivníůPlátno samozřejmě nepracuje, tj. nic nekreslí – nakreslit se musejí jednotlivé objekty samy. Aktivní plátno pouze celé kreslení organizuje. Řekne objektu, kdy se má nakreslit a předá mu kreslítko, kterým se pak nakreslí. Aby se třída mohla takto chovat, potřebuje vědět dvě věci: které objekty se mají na plátně zobrazovat, kdy se stav některého z nakreslených objektů změnil natolik, že je třeba plátno překreslit. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 241 z 433 242 Myslíme objektově v jazyku Java 1.5 Jak jsem již řekl, nyní již nebudeme našim objektům říkat, aby se nakreslily. Budeme-li chtít, aby byl náš objekt na plátně nakreslen, požádáme třídu AktivníPlátno, aby jej zařadila mezi objekty, o jejichž vykreslení se stará. Kdykoliv se nyní na plátně cokoliv změní, tak AktivníPlátno postupně požádá všechny spravované objekty, aby se překreslily. Požádá-li je o to ve správném pořadí, budou objekty bez problému přes sebe přejíždět a pod sebou podjíždět a v každém okamžiku bude vše správně nakresleno. Aby mohla instance třídy AktivníPlátno takto pracovat, musíme jí slíbit, že náš objekt bude schopen se na požádání nakreslit. Přesněji, že bude mít ve své „sbírce“ metodu, jež bude schopna převzít od třídy AktivníPlátno kreslítko (objekt třídy java.awt.Graphics2D) a s jeho pomocí se nakreslit. Asi nyní namítnete, že v názvu třídy java.awt.Graphics2D jsou tečky, o kterých jsem se při výkladu pravidel pro tvorbu identifikátorů nezmínil (viz pasáž Pravidla pro tvorbu identifikátorů v jazyce Java na straně 46). Tyto tečky nejsou součástí vlastního identifikátoru třídy, ale slouží pouze k oddělení částí popisujících, jak má virtuální stroj najít danou třídu v knihovně. Podrobněji si o těchto záležitostech budeme povídat v kapitole Budete si to přát zabalit? na straně 376. Kreslítko, jež aktivní plátno předává objektu, který žádá o nakreslení se, je zároveň vynikajícím zabezpečovacím prostředkem, který nám dokáže zaručit, že se nikdo jiný na plátno nenakreslí, tj. že se nikdo nenakreslí, aniž by byl požádán aktivním plátnem. Kdo nedostane kreslítko, ten se nemůže nakreslit. Jediný, od koho lze kreslítko získat, je aktivní plátno, a to je předá vždy pouze tomu objektu, který se podle něj má v danou chvíli nakreslit. 6.2 Rozhraní jako zvláštní druh třídy Jistě vás zajímá, jak takový slib realizujeme. Pomůžeme si zvláštním druhem třídy označované jako rozhraní (interface). Tento termín jsme již zavedli, když jsme hovořili o zapouzdření (viz podkapitola Zapouzdření na straně 128) a o dokumentaci (viz podkapitola Komentáře a dokumentace na straně 148). Tehdy jsme si řekli, že „rozhraní třídy budeme chápat jako množinu informací, které o sobě třída zveřejní“. Způsob, jakým je dané rozhraní naprogramováno jsme označili jako implementaci. Tato terminologie zůstane v platnosti i v okamžiku, kdy budeme hovořit o rozhraní jako o zvláštním druhu třídy. Rozhraní totiž na rozdíl od obyčejných tříd @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 242 z 433 Kapitola 6: Rozhraní 243 neříká vůbec nic o tom, jak budou jednotlivé metody implementovány. Pouze deklaruje seznam veřejných metod, které budou jeho „instance“ implementovat. Jinými slovy: deklaruje, na jaké zprávy budou umět jeho „instance“ reagovat. V našem novém projektu je např. definováno rozhraní IKreslený. Jeho definice vypadá (bez oddělovacích komentářů) následovně: 1 public interface IKreslený 2 { 3 /*************************************************************************** 4 * Za pomoci dodaného kreslítka vykreslí obraz své instance 5 * na animační plátno. * 6 7 * @param kreslítko Kreslítko, kterým se instance nakreslí na plátno. 8 */ 9 void nakresli( java.awt.Graphics2D kreslítko ); 10 11 }//public interface IKreslený V tomto kurzu budu všechna rozhraní označovat identifikátory začínajícími na I. Přiznám se, že jsem tento zvyk převzal z dob, kdy jsem ještě programoval v C++, kde se nejrůznější předpony používaly poměrně často. V javové komunitě sice zdobení identifikátorů prefixy nebývá zvykem, ale podle mne je pro začátečníky výhodné, když na první pohled poznají, zda se jedná o identifikátor třídy nebo rozhraní. Proto se v tomto textu setkáte s podobnými „návodnými značeními“ ještě několikrát. V definici rozhraní si všimněte dvou odchylek od definice standardní třídy: V hlavičce třídy je místo klíčového slova class použito klíčové slovo interface, jímž překladači oznamujeme, že nepřekládá standardní třídu ale rozhraní. V těle rozhraní je zapsána pouze hlavička metody ukončená středníkem. Tělo metody je totiž záležitostí implementace a jak jsme si již řekli, o tu se rozhraní nestará. Ti bystřejší z vás si možná ještě všimli toho, že u metody není uveden modifikátor public. Je to proto, že všechny metody deklarované v rozhraní jsou automaticky veřejné. Překladač proto na explicitním (=veřejném, zjevném) zapsání tohoto modifikátoru netrvá. Když jej ale uvedete, nic tím nepokazíte. Pokazit byste mohli program pouze tím, že byste zde uvedli jiný modifikátor než public. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 243 z 433 244 6.3 Myslíme objektově v jazyku Java 1.5 Instance rozhraní V první kapitole jsme si říkali, že se všechny objekty dělí do tříd. V další kapitole jsme si prozradili, že každá třída musí obsahovat konstruktor, což je předpis, podle nějž se vytvářejí její instance. Jakýkoliv definovaný postup, tedy i konstruktor, ale nutně patří do implementace. Nemá-li rozhraní obsahovat žádnou implementaci, nemůže obsahovat ani konstruktor a tím pádem nemůže být možno vytvořit jeho instanci. Řeknete si asi: „K čemu nám tedy může být dobré, když nemůžeme vytvořit jeho instanci?“ Rozhraní sice svou instanci vytvořit nemůže, ale Java dovoluje, aby se za jeho instance vydávaly instance „obyčejných“ tříd. Takováto třída se ale musí veřejně přihlásit k tomu, že bude dané rozhraní implementovat, aby se to o ní překladač dozvěděl a mohl zkontrolovat, že opravdu implementuje všechny metody, které dané rozhraní deklaruje. Odměnou jí za to bude možnost vydávat svoje instance za instance implementovaného rozhraní. (Umějí vše, co rozhraní slibovalo, že jeho instance budou umět, takže jim to nedělá potíže.) Chcete-li pro rozhraní příklad z lidské společnosti, mohli bychom říct, že rozhraní je takový ideolog. Vyhlásí, jak by se něco mělo dělat, a nechá na svých stoupencích, aby jeho „myšlenky“ realizovali (=implementovali). Kdybychom se znovu vrátili k naší analogii se světem robotů a jejich vozidel-instancí, tak bychom mohli rozhraní považovat za něco jako licenci pro instance. Chce-li továrna vyrábět vozidla s osádkami, které mohou pracovat např. jako malíři, požádá zastupitelstvo o licenci (deklaruje implementaci rozhraní) a prokáže-li splnění všech požadovaných podmínek (tj. přítomnost robotů-metod, kteří zvládají činnosti požadované pro daný druh licence), mohou její vozidla přijímat od ostatních instancí zakázky pro licencovanou činnost – v našem případě pro malíře. 6.4 Nový projekt Tak dost již teorie a pojďme se podívat, jak bychom jak se taková implementace rozhraní třídou řeší v praxi. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 244 z 433 Kapitola 6: Rozhraní 245 Otevřete si projekt 06_Rozhraní_A a pojďte se nejprve podívat, jak implementace rozhraní projeví v diagramu tříd. Po prvním otevření by měl projekt vypadat jako na obr. 6.1. Jak vidíte, výchozí projekt nám oproti minulé kapitole zase trochu zesložitěl. Vedle toho, že třídu Plátno nahradila třída AktivníPlátno, tak v něm ještě přibyly třídy Čára a Text a především pak rozhraní IKreslený. Všimněte si, že rozhraní je v diagramu tříd reprezentováno trochu jinak než běžná třída: Obrázek 6.1 Podoba projektu 06_Rozhraní_A po prvním otevření @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 245 z 433 246 Myslíme objektově v jazyku Java 1.5 Ti, kteří aplikovali konfiguraci RUP, si především všimnou, že rozhraní je zelené (ve standardní konfiguraci má rozhraní stejnou barvu jako běžné třídy). V horní části obdélníku reprezentujícího rozhraní není pouze název rozhraní, ale nad ním je ještě text «interface», který slouží k rozpoznání rozhraní i v případě, kdy jeho ikona není od ikon běžných tříd barevně odlišena. Text uzavřený ve «francouzských uvozovkách» (zdvojených špičatých závorkách) je v jazyku UML označován jako stereotyp a slouží k bližší specifikaci typu daného objektu – v našem případě rozhraní. Podíváte-li se do složky s projektem, zjistíte, že z hlediska vytvořených souborů se rozhraní chová jako obyčejná třída – i ono má definováno svůj zdrojový soubor s příponou java a přestože nemá definovánu implementaci, při jeho překladu se vytvoří class-soubor s příponou class. Počet tříd v projektu je již tak velký a vzájemné vazby tak hojné, že už byl docela problém uspořádat třídy tak, aby šipky jejich závislostí byly ještě relativně sledovatelné. Řekl bych, že zrovna v tomto projektu jsou vzájemné vazby natolik zřejmé, že bychom se jistě obešli i bez toho, že by byly podrobně vykreslené. Toho se dá dosáhnout poměrně snadno: stačí v nabídce Zobrazit zrušit volbu Zobrazit "Používá" (obr. 6.2). Obrázek 6.2 V nabídce Zobrazit je možno zrušit volbu Používá a nastavit volbu Dědičnost Po zrušení volby zmizí všechny čáry. Při rušení jste si však mohli všimnout, že pod volbou Zobrazit "Používá" je volba Zobrazit "Dědičnost". Po nastavení této volby se @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 246 z 433 Kapitola 6: Rozhraní 247 zobrazí šipky s trojúhelníkovou hlavičkou, které vedou od tříd, jež implementují rozhraní IKreslený k tomuto rozhraní (viz obr. 6.3). To je ale informace, která pro nás má v daném okamžiku daleko větší vypovídací hodnotu, než šipky závislostí, jejichž zobrazování jsme před chvílí zrušili, protože nyní např. poznáme, které instance můžeme přihlásit do správy aktivního plátna. Uspořádání tříd na pracovní ploše, které vycházelo ze snahy o maximálně přehledné zobrazení vzájemných závislostí, ale nyní působí poněkud chaoticky. Nic nám však nebrání třídy přeuspořádat např. podle obr. 6.4. Jak vidíte, diagram se výrazně zpřehlednil. Obrázek 6.3 Diagram tříd po zrušení zobrazování závislostí a nastavení zobrazování dědičnosti @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 247 z 433 248 Myslíme objektově v jazyku Java 1.5 Práce s novým plátnem Než si začneme vysvětlovat, jak zařídíme, aby se instance našich tříd mohly vydávat za instance daných rozhraní, ukážeme si nejdříve, jaké změny potkaly náš projekt po zavedení aktivního plátna. Zkusíme si proto několik jednoduchých operací: 1. První změna je, že při práci s objekty na plátně potřebujeme mít v zásobníku odkazů odkaz na aktivní plátno. Proto začněte tím, že třídě AktivníPlátno pošlete zprávu getPlátno(), která vám vrátí odkaz na plátno. Ten uložte do zásobníku odkazů a nazvěte jej třeba AP. Při té příležitosti si všimněte, že plátno v aplikačním okně je rozděleno pravidelnou sítí čar. Ta nám umožní lepší orientaci v souřadnicích. Obrázek 6.4 Přeuspořádaný diagram tříd 2. Pomocí implicitního konstruktoru vytvořte instanci obdélníka. 3. Vytvořený obdélník se ale na plátně neobjeví. Nikdo jej totiž ještě do správy plátna nepřihlásil. Učiňte tak – pošlete plátnu zprávu přidej(obrazec), které jako parametr zadáte vytvořený obdélník. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 248 z 433 Kapitola 6: Rozhraní 249 4. Obdélník se objeví na v levém horním rohu plátna.Všimněte si, že vytvořený obdélník bude zabírat právě dvě vodorovně sousedící pole plátna. Kromě toho plátno BlueJ otevře dialogové okno, v němž oznámí, že plátno vrátilo logickou hodnotu true, což (jak si můžete přečíst v dokumentaci) znamená, že plátno dotyčný obrazec ve své správě ještě nemělo a přidalo jej mezi spravované. 5. Obdobným způsobem vytvořte a poté přihlaste „implicitní elipsu“. 6. Obdobně vytvořte a přihlaste i „implicitní trojúhelník“. Pokud jste dělali vše tak, jak jsem říkal, měly by výsledná podoba plátna nyní odpovídat obr. 6.5. Zkuste nyní s plátnem a obrazci na něm trochu experimentovat: Obrázek 6.5 Implicitní velikost obrazců je odvozena z velikosti polí aktivního plátna Pohybujte jednotlivými vytvořenými obrazci (tj. volejte jejich posunové metody) a ověřte, že se obrazce nepřemazávají a že dokonce při přesunech zachovávají vzájemnou „hloubku“, tj. že obdélník bude vždy spodní a naopak trojúhelník vždy horní. Zároveň si všimněte, že bezparametrické přesuny posouvají obrazce právě o velikost jednoho pole. Požádejte plátno, aby změnilo svůj rozměr. U aktivního plátna však již nebude zadávat rozměr v bodech, ale v polích. Požádejte jej, aby nastavilo šířku na 4 políčka a výšku na 3 políčka. Všimněte si, že po změně rozměru plátna zůstávají obrazce stále nakresleny. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 249 z 433 250 Myslíme objektově v jazyku Java 1.5 Nastavte nyní plátnu velikost kroku např. na 10 bodů a všimněte si, jak se podle velikosti kroku změní vzdálenost čar mřížky. Zkuste znovu pohybovat obrazci a ověřte, že bezparametrické posunové metody stále posunují obrazci o velikost jednoho pole. Projděte dokumentaci všech tříd a vyzkoušejte nové metody a ověřte, nakolik jejich chování dopovídá očekávanému. Událostmi řízené programování Tady bych vás chtěl upozornit na jednu drobnost. Předpokládám, že jste si všimli, že instance, která je předána do péče aktivního plátna, vůbec neví, kdy bude požádána o to, aby se nakreslila. Vy předáte objekt aktivnímu plátnu a vesele si dále řešíte svůj program a necháváte na aktivním plátnu, aby vaší instanci ve vhodnou chvíli oznámilo, že se má překreslit. Takovýto způsob řešení problémů bývá označován jako událostmi řízené programování. Instanci přihlášenou u aktivního plátna bychom mohli označit za posluchače, který naslouchá dění a čeká, až nastane ta správná událost. Tato událost nastane v okamžiku, kdy se aktivní plátno dozví, že se má překreslit. Je přitom úplně jedno, jestli se bude překreslovat proto, že se nějaký objekt na plátně posunul a řekl plátnu, že se musí překreslit, protože jeho obsah již vypadá jinak, nebo jestli plátno požádal o překreslení operační systém, protože jste se rozhodli okno s plátnem posunout na obrazovce o kousek vedle a vše se proto musí nakreslit v nové pozici. Je jedno, jak došlo k události vyvolávající překreslení plátna. Plátno zareaguje vždy stejně: oznámí jednotlivým posluchačům, že k události došlo, a že oni mají udělat to, kvůli čemu se přihlásili – překreslit se. A protože aktivní plátno umí požádat své posluchače vždy ve správném pořadí, bude obsah vždy správně vykreslen. 6.5 Implementace rozhraní Tak jsme si pohráli a měli bychom zase začít trochu programovat. Přidejte do projektu třídu Strom z projektu 05_Vzory (v diagramu tříd na ni máme vlevo dole připravené místo). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 250 z 433 Kapitola 6: Rozhraní 251 Nyní se pokusíme tuto třídu přizpůsobit podmínkám nového projektu. Nejprve zařídíme, aby se třída přihlásila k implementaci rozhraní IKreslený. Máme dvě možnosti jak to zařídit: Vytvořit implementační šipku v diagramu tříd. Zapsat informaci o deklarovaném rozhraní přímo do hlavičky třídy. Implementace rozhraní v diagramu tříd Začneme tou jednodušší, kterou je natažení implementační šipky v diagramu tříd. Postup je velmi jednoduchý: 1. Klepněte v levém panelu na tlačítko Vytvořit vztah "Dědičnost" se šipkou s trojúhelníkovou hlavičkou (viz obr. 6.6). Obrázek 6.6 První krok při natahování šipky dědičnosti 2. V dolním okraji okna projektu se objeví text Vyberte podtřídu (dceřinou třídu), která dědí, resp. implementuje. Poslechněte jej a klepněte na třídu Strom. 3. V dolním okraji okna projektu se objeví text Vyberte nadtřídu (rodičovskou třídu, rozhraní), ze které se dědí. Opět uposlechněte a klepněte na rozhraní IKreslený. Všimněte si přitom, že při pohybu myší za ní BlueJ hned natahuje budoucí šipku. Jak prosté, že? Někomu bude možná lépe vyhovovat, když po stisku tlačítka Vytvořit vztah "Dědičnost" najede na implementující třídu, tam stiskne tlačítko myši, se stisknutým tlačítkem přejede na implementované rozhraní, kde tlačítko myši pustí. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 251 z 433 252 Myslíme objektově v jazyku Java 1.5 Kdybyste později dospěli k závěru, že třída dané rozhraní implementovat nemá, stačí klepnout pravým tlačítkem na implementační šipku. Šipka pak „ztloustne“ a objeví se u ní místní nabídka s jediným příkazem: Odstranit. Jeho zadáním šipku zrušíte (vyzkoušejte si to). Implementace rozhraní ve zdrojovém kódu Přímý zápis do kódu není o nic složitější. Ukážeme si, jak to BlueJ zařizuje za nás. Protože jsme před právě odstranili implementační šipku, můžeme si celý proces ukázat od začátku. 1. Otevřete zdrojový kód třídy Strom a upravte jeho rozměr a polohu tak, aby se vám okno editoru nepřekrývalo s oknem projektu. 2. Najeďte posuvníkem tak, abyste v okně editoru viděli hlavičku třídy. 3. Natáhněte v okně projektu implementační šipku od třídy Strom k rozhraní IKreslený. Všimněte si, že BlueJ okamžitě doplnil do hlavičky „implementační dovětek“, takže hlavička má nyní tvar: public class Strom implements IKreslený 4. Odstraňte implementační šipku a všimněte si, že „implementační dovětek“ z hlavičky zase zmizel. 5. Zkuste to nyní obráceně a dopište implementační dovětek do hlavičky třídy „ručně“. 6. Stiskem CTRL+S zdrojový kód uložte a všimněte si, že BlueJ okamžitě doplnil do diagramu tříd implementační šipku. 7. Smažte „implementační dovětek“ a přesvědčte se, že po následném uložení souboru implementační šipky opět zmizela. 8. Na závěr svého experimentování implementační šipku opět vytvořte. 6.6 Úprava zdrojového kódu třídy Strom Třída Strom se tedy přihlásila k implementaci rozhraní IKreslený. Nyní už nám zbývá pouze upravit její zdrojový text tak, aby jej byl překladač ochoten přeložit. Pojďme jej tedy společně upravit. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 252 z 433 Kapitola 6: Rozhraní ☯ 253 V této kapitole s vámi půjdu při úpravách kódu krok za krokem, abyste poznali, jak takový proces většinou probíhá. Dopředu upozorňuji, že to nebude jenom cesta přímá, protože ani při úpravě svých programů většinou nepůjdete přímo. Při úpravách programů se často stává, , že jednou něco upravíme a za chvíli tuto úpravu zrušíme a nahradíme ji úpravou jinou. Říkám vám to dopředu, abyste pak nebyli překvapeni. Řada začínajících programátorů mívá komplexy z toho, kolik má ve svých programech chyb. Druhá, neméně početná skupina je zase přesvědčena, že cokoliv napíše, je nutně bez chyby. Největší a nejčastější začátečnickou chybou je to, když programátor začne hledat chybu místo ve svém programu v překladači, operačním systému nebo zlomyslných kolezích. Tato podkapitola bude možná některým z vás připadat dlouhá. Chtěl bych vám v ní však přiblížit, jak většinou probíhá typický proces ladění programu a pokusit se vás vystříhat výše zmíněné nejčastější začátečnické chyby. Třída musí jít přeložit Nemá smysl dlouze bádat nad tím, co se má a nemá upravit. Nejlepšími rádci jsou vždy překladač a správný testovací program. Začneme proto tím, že požádáme o překlad našeho kódu. Při první žádosti o překlad se překladač zastaví na řádku s hlavičkou třídy a v dolním informačním poli oznamuje: Strom is not abstract and does not override abstract method nakresli(Java.awt.Grapnics2D) IKreslený in Zrovna k této chybě sice nápověda neexistuje, ale z toho, co jsme si před chvílí říkali asi sami odhadnete, že se překladači nelíbí, že třída Strom sice ve své hlavičce deklarovala, že implementuje rozhraní IKreslený, avšak neimplementovala jeho metodu. Pojďme to napravit – upravíme stávající metodu nakresli tak, aby vyhovovala požadavkům rozhraní a tím i aktivního plátna. Změna naštěstí nebude příliš složitá a neměla by nás ani překvapit, protože jsme ji vlastně naplánovali. Stávající metodě proto přidáme požadovaný parametr (kreslítko) a ten předáme metodám, které nakreslí korunu a strom (bez kreslítka by se nyní již nakreslit neumějí). Kromě toho tuto metodu ve zdrojovém souboru přeřadíme do sekce, kam budeme dávat metody vyžadované implementovanými rozhraními. Výsledný prográmek tedy vypadá následovně: @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 253 z 433 254 Myslíme objektově v jazyku Java 1.5 1 //== PŘEKRYTÉ METODY IMPLEMENTOVANÝCH ROZHRANÍ ================================= 2 /*************************************************************************** 3 * Vykreslí obraz své instance na plátno. 1 * 4 * @param kreslítko Objekt, jehož prostřednictvím se má instance nakreslit. 5 */ 6 public void nakresli(java.awt.Graphics2D kreslítko) 7 { 8 10 koruna.nakresli(kreslítko); kmen .nakresli(kreslítko); 11 12 } Takže pokračujeme dál a požádáme o další překlad. Tentokrát nám překladač zastaví na řádku Plátno.getPlátno().setRozměr( šířka, výška ); kde nám oznámí: cannot resolve symbol – variable Plátno Nápověda by vám prozradila, že se používáte proměnnou, která není deklarovaná, ale při pohledu na označený řádek vám bude hned jasné, že chyba je jinde: v programu používáme třídu Plátno, která v daném projektu vůbec není definována. Místo ní tu přece máme AktviníPlátno. Dopředu víme, že tato chyba se bude v souboru vyskytovat vícekrát. Potřebovali bychom projít celý soubor a všechny výskyty obyčejného plátna nahradit aktivním plátnem. BlueJ nám k tomu naštěstí nabízí prostředky. Přesuňte se na začátek souboru a stiskněte v okně editoru na liště s tlačítky tlačítko Najít… Otevře se dialogové okno, které vyplňte podle obr. 6.7, tj. zadejte vyhledávání slova Plátno, jeho nahrazení slovem AktivníPlátno s respektováním velkých písmen a vyhledáváním pouze celých slov. Stiskem tlačítka NajraditVše požádejte o hromadné nahrazení tohoto slova. Obrázek 6.7 Dialogové okno pro vyhledání a nahrazení textu Editor provede, co jsme mu přikázali a v informačním poli vypíše: @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 254 z 433 Kapitola 6: Rozhraní 255 Replaced 4 instances of Plátno z čehož pochopíme, že našel a nahradil 4 výskyty slova Plátno. Jak jste si mohli všimnout v dialogovém okně Najít, editor umí hledat pouze nahoru a dolů. Proto jsme se na před vyvoláním tohoto okna přesouvali na začátek souboru. Druhou možností je nikam se nepřesouvat, vyhledat a nahradit vše směrem dolů, změnit hodnotu přepínače na Hledat nahoru a vyhledat a nahradit vše ještě směrem nahoru. Požádáme znovu o překlad. Tentokrát se překladač zastaví na příkazu koruna.nakresli() a v informačním poli nám oznámí: nakresli(Java.awt.Graphics2D) in Elipsa cannot be applied to () Nápověda vám prozradí, že používáte takovou sadu parametrů, pro kterou není definována přetížená verze metody. Podíváte-li se ale na označený příkaz, je to zřejmé: před chvílí jsem přece říkal, že nyní už se nikdo neumí nakreslit bez toho, že by obdržel kreslítko. Elipsa proto metodu nakresli() vůbec nedefinuje. Tak babo raď – co s tím? Musíme na to úplně jinak. Na počátku kapitoly jsme si říkali, že nyní se už nikdo nebude kreslit sám od sebe, ale vždy pouze na výzvu aktivního plátna, které mu k tomu předá příslušné kreslítko. Tím aktivní plátno zařídí, že nikdo nezmění obraz na plátně, aniž by se o tom dozvědělo – jinak by totiž za něj nemohlo ručit Podíváte-li se do dokumentace aktivního plátna, zjistíte, že tyto věci řeší tak, že každý, kdo se rozhodne, že by měl vypadat jinak, než je právě zobrazován, požádá aktivní plátno o to, aby se znovu překreslilo. Když je pak tento objekt v rámci překreslování požádán, aby se nakreslil, nakreslí se v nové podobě. Z toho vyplývá i řešení našeho problému: kdykoliv se změní vzhled nebo pozice objektu, musí požádat aktivní plátno, ať se překreslí. Metodu setPozice(int, int), ve které k chybě došlo, bychom tak mohli přepsat do tvaru: 1 public void setPozice(int x, int y) 2 { 3 koruna.setPozice( x, y ); 1 kmen .setPozice( x + (koruna.getŠířka() - kmen.getŠířka()) / 2, 4 y + koruna.getVýška() ); 5 AktivníPlátno.getPlátno().překresli(); 6 } Při dalším pokusu o překlad se překladač zarazí na příkazu koruna.smaž(); @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 255 z 433 256 Myslíme objektově v jazyku Java 1.5 o němž tvrdí: cannost resolve symbol – method smaž() Nové základní obrazce kreslené na aktivní plátno totiž nemají metody nakresli() a smaž(), aby nedocházelo k záměně nové a staré metody nakresli(). Pokud bychom chtěli náš strom z plátna opravdu smazat, museli bychom to stejně dělat jinak: museli bychom požádat plátno, aby jej odstranilo ze seznamu těch, které spravuje. Metoda by pak získala tvar: 1 2 3 4 5 6 7 /*************************************************************************** * Odstraní obraz své instance z plátna. */ public void smaž() { AktivníPlátno.getPlátno().odstraň( this ); } Další pokus o překlad kódu skončí opět chybou. Překladač se opět zarazí ne příkazu, v němž po koruně chceme, aby se nakreslila. Tentokrát je to v metodě posunDolů(int): 1 public void posunDolů( int vzdálenost ) 2 { 3 koruna.posunDolů( vzdálenost ); 4 kmen .posunDolů( vzdálenost ); 5 koruna.nakresli(); 6 } Tuto chybu bychom mohli opravit stejně, jako jsme ji upravovali v metodě setPozice(int,int). Jenže když trochu zavzpomínáte, tak si možná vybavíte, že jsme zde korunu kreslili proto, že nám při některých přesunech její část odmazával kmen. Když ale teď aktivní plátno ručí za to, že vše bude nakresleno správně, můžeme vše opravit jednoduše tak, že tento příkaz prostě odmažeme. Další pokus o překlad již prochází – první etapu úpravy našeho zdrojového kódu máme tedy za sebou. Testování Třídu jsme upravili, jdeme tedy upravovat testy. Mohli bychom sice vytvořit nové, ale když jsme do přípravy stávajících testů vložili tolik námahy, tak většinou chceme, aby se nám vyplatila. Pokusíme se tedy zprovoznit staré testy v novém projektu. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 256 z 433 Kapitola 6: Rozhraní 257 Načtěte proto z minulého projektu třídu StromTest a otevřete její zdrojový text. Zkusíme postupovat stejně jako v případ třídy Strom – pokusíme se třídu v novém projektu přeložit. Na počátku se nám překladač opět vzbouří, že nezná třídu Plátno. Tuto chybu ale již známe a víme jak ji řešit: odkaz na třídu Plátno nahradíme odkazem na AktivníPlátno. Při dalším překladu se překladači nebude líbit, že instance třídy Strom nemají definovanou metodu nakresli(). Tady se musíme zamyslet – máme totiž dvě možnosti: Místo abychom strom kreslili, požádáme aktivní plátno, aby příslušnou instanci přidala do svého seznamu. Řekneme si, že když může mít instance definovanou metodu smaž(), tak bychom jí mohli definovat i metodu nakresli(), která by měla za úkol přihlásit svoji instanci u aktivního plátna. Mně se líbí víc druhá možnost, protože naznačuje, že by nám mohla občas ušetřit nějaké to psaní. Otevřeme proto znovu zdrojový kód třídy Strom a přidáme do něj metodu nakresli(): 1 2 3 4 5 6 7 /*************************************************************************** * Přihlási instanci u aktivního plátna do jeho správy. */ public void nakresli() { AktivníPlátno.getPlátno().přidej( this ); } Přeložíme třídu Strom a po úspěšném překladu se znovu pokusíme přeložit třídu StromTest. A vida – tentokrát ji překladač bez protestů celou přeloží. Jdeme tedy testovat. Začneme s prvním testem – NakresliSmaž. A hned výbuch – test neprošel a okno Výsledky testů nám oznamuje, že jsme špatně zadali nějaké rozměry. Podíváme-li se podrobně do výpisu chyb ve spodní polovině okna, zjistíme, že k chybě mělo dojít na 48. řádku třídy StromTest v metodě setUp(). Na tomto řádku se pouze nastavuje rozměr aktivního plátna – věc, kterou jsme s obyčejným plátnem dělali mnohokrát. Nejlepší bude zaběhnout do dokumentace a podívat se, co můžeme dělat špatně. A jsme doma! V dokumentaci si totiž můžeme přečíst, že metoda setRozměr(int,int) nastavuje políčkovou velikost plátna – my jsme po plátnu chtěli, aby bylo 300 polí široké a 300 vysoké. Změníme proto nastavovanou velikost plátna na 6×6 polí a zkusíme naše testy znovu. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 257 z 433 258 Myslíme objektově v jazyku Java 1.5 Obrázek 6.8 Z posledního řádku vyčteme, že k chybě došlo v metodě setUp třídy Strom na řádku 48 Test tentokrát bez problému prošel. Zkusíme tedy další v řadě, kterým je test posunů. Tady opět zakopáváme: dialogové okno sice vyhrožuje, že bude následovat posun, ale stromy se ani nehnou. Zkusíme, jestli nám testy navzájem neinterferují (tj. jestli první po sobě nezanechal něco, co by druhému bránilo v činnosti). Zresetujeme virtuální stroj a spustíme test posunů znovu. Ještě horší – tentokrát se stromy ani neobjevily. Otevřeme proto zdrojový kód testovací třídy a podíváme se, co může být špatně. Po chvilce přemýšlení byste na to jistě přišli: my jsme sice v této metodě vytvořili instance stromů, ale žádnou z nich jsme nepřihlásili do správy plátna. Jak víme, aktivní plátno zobrazuje pouze ty objekty, které se přihlásí do jeho správy. Náprava je jednoduchá: do metody setUp() zavoláme pro každý vytvořený strom jeho metodu nakresli() (abychom to měli úplně jednoduché, můžeme potřebnou čtveřici příkazů zkopírovat z metody testNakresliSmaž()) a zkusíme test spustit znovu. Tentokrát proběhlo vše bez chyby. Následující test nastavení kroku proběhne sice bez chyby, ale hned další test Zarámuj opět skončí „v červených“ a opět pro špatně zadané rozměry. Jak si zjistíme v okně Výsledky testů, chyba je nyní nejspíše v metodě zarámuj(). Podíváme se do ní a je nám to jasné. Je tu stejná chyba jako na počátku – opět nastavujeme bodovou velikost plátno místo políčkové. Tady ale nemůžeme říct, kolik polí má být plátno velké, protože velikost našeho stromu nemusí být násobkem velikosti pole. Nahlédneme proto do dokumentace, jestli bychom tam nenašli něco, co by nám mohlo pomoci. A vskutku – pro tento účel by se nám krásně hodila metoda @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 258 z 433 Kapitola 6: Rozhraní 259 setKrokRozměr(int,int,int), kterou můžeme současně nastavit i velikost kroku a tím i velikost jednoho pole. Zadáme proto velikost kroku rovnu jedné a rozměry pak mohou zůstat takové, jaké jsme je nastavovali dříve. Spustíme test. První strom nám metoda zarámuje dobře, ale druhý stroj se začne přetahovat o své místo na slunci s kolegy (viz obr. 6.9). Obrázek 6.9 Při pokusu o zarámování druhého stromu nám na obraze překážejí jeho kolegové Za vše může aktivní plátno, protože se neustále stará o to, aby vše, co přihlásíme a nesmažeme, bylo zobrazeno. Musíme proto upravit metodu zarámuj tak, že nejprve požádá plátno, aby všechny ostatní obrazce z plátna odstranilo a pak přihlásí svoji instanci, kterou nechá zobrazit a zarámovat. Výsledná podoba metody zarámuj tedy bude vypadat následovně: 1 2 3 4 5 6 7 8 9 10 11 /*************************************************************************** * Odstraní z plátna všechny ostatní instance a nastaví * parametry okna s plátnem tak, aby právě zarámovalo danou instanci. */ public void zarámuj() { AktivníPlátno.getPlátno().odstraňVše(); AktivníPlátno.getPlátno().setKrokRozměr( 1, getŠířka(), getVýška() ); setPozice( 0, 0 ); AktivníPlátno.getPlátno().přidej( this ); } Tak znovu přeložíme a vyzkoušíme. Funguje! Paráda, můžeme pokračovat dalším testem. Spustíme test PočítáníInstancí. Jenže ouvej – plátno vypadá po spuštění testu nějak degenerovaně (viz obr. 6.10)! Protože tento test pouze vypisuje textovou podobu instancí, bude chyba asi opět v přípravku, tj. v metodě setUp(). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 259 z 433 260 Myslíme objektově v jazyku Java 1.5 Obrázek 6.10 Plátno v průběhu testů zdegenerovalo Při pohledu na její zdrojový text je vše jasné. Metoda nastavuje velikost plátna 6×6 polí, jenže metoda zarámuj prověřovaná v minulém testu zadala velikost kroku rovnu 1. Přípravek tedy správně generoval plátno velikosti 6×6 bodů. Musíme změnit metodu nastavující počáteční velikost plátna a zadat nejenom jeho políčkový rozměr, ale také velikost políčka. Spustíme nový test a opět v pořadí Zarámuj – PočítáníInstancí. Okno při něm má již správnou velikost, ale na plátně zbyl ještě strom od minula. Zapomněli jsme, že před přihlášením stromů přípravku musím plátno nejprve vyčistit. Navíc se již na plátně neobjevují čáry, které ohraničovaly jednotlivá pole a usnadňovaly nám tak kontrolu velikosti a pozice jednotlivých obrazců. Je to proto, že při nastavení velikosti kroku rovné jedné se zároveň vypne zobrazování těchto čar. Chceme-li je vidět, musíme je znovu zapnout zavoláním metody setMřížka(boolean). Znovu upravíme metodu setUp() a všechny objevené chyby napravíme. Po této úpravě bude vypadat následovně: 1 protected void setUp() 2 { AktivníPlátno plátno = AktivníPlátno.getPlátno(); 3 plátno.setKrokRozměr( 50, 6, 6 ); 4 plátno.setMřížka( true ); 5 6 plátno.odstraňVše(); strom1 = new Strom( 0, 0, 100, 150, 10, 3 ); 7 strom2 = new Strom( 0, 150, 100, 150, 5, 4 ); 8 strom3 = new Strom( 100, 100, 200, 200, 20, 2 ); 9 strom4 = new Strom( 100, 0, 150, 100, 3, 5 ); 10 strom1.nakresli(); 11 strom2.nakresli(); 12 13 strom3.nakresli(); strom4.nakresli(); 14 15 } Nyní se již počítání instancí rozběhlo. Zbývá poslední test, kterým je vygenerování zarámovaného obrázku ve středu plátna. A jako obyčejně, test opět nechodí – žádný zarámovaný strom nikde není. Předpokládám, že již tušíte, kde hledat chybu: rám i strom se zapomněly přihlásit u animačního plátna. Opravíme proto metodu obrázek(Oblast,int) – její nová podoba může být např. následující: @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 260 z 433 Kapitola 6: Rozhraní 261 1 public static void obrázek( Oblast oblast, int šířkaRámu ) 2 { AktivníPlátno.getPlátno().přidej( new Obdélník(oblast, Barva.ČERNÁ) ); 3 oblast.x += šířkaRámu; 4 5 oblast.y += šířkaRámu; oblast.šířka -= 2*šířkaRámu; 6 7 oblast.výška -= 2*šířkaRámu; 8 9 AktivníPlátno.getPlátno().přidej( new Obdélník(oblast, Barva.AZUROVÁ) ); 10 new Strom( oblast ).nakresli(); 11 } Všechny metody instancí jsme se snažili zahrnout do našich testů. Vytvoření testů však odolaly statické metody zarámuj(int,int) a alej(). Když se je pokusíte spustit, zjistíte, že jsou v nich samozřejmě opět známé chyby. Jejich odhalení, odstranění a doplnění potřebných testů (abychom příště nemuseli myslet na to, že pro tyto metody nemají svoje testy) vám dám za domácí úkol. Podobu tříd Strom a StromTest ve stavu, do kterého jsme je právě doprovodili (včetně domácího úkolu) najdete v projektu 06_Rozhraní_Z ve třídách Strom_6a a Strom_6aTest. Závěrečné úpravy Třídu i její testy jsme tedy rozchodili. Nyní bychom ještě měli proběhnout její zdrojový kód a podívat se, jestli bychom něco nemohli udělat lépe. V následujících odstavcích budu většinou pouze naznačovat, co by se dalo vylepšit nebo upravit. Vlastí realizaci nechám většinou na vás. Uložení odkazu na Plátno do atributu třídy První věc, které bychom si mohli všimnout, je skutečnost, že ve třídě často pracujeme a aktivním plátnem, které vždy získáváme pomocí metody getPlátno(). Toto plátno se ale nemůže změnit – víme přece, že je jedináček. Mohli bychom proto kód vylepšit tak, že bychom si zavedli statickou konstantu (pojmenujme ji třeba AP), do ní uložili odkaz na plátno a v celém kódu pak sekvenci AktivníPlátno.getPlátno() nahradili odkazem AP. Obdobnou změnu bychom mohli udělat i v testovací třídě – přidali bychom do přípravku proměnnou AP a uložili do ní odkaz na aktivní plátno. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 261 z 433 262 Myslíme objektově v jazyku Java 1.5 Odstranění statického atributu krok Třídy základních obrazců již v tomto projektu nemají statický atribut krok, protože odvozují délku posunu bezparametrických posunových metod od velikosti pole aktivního plátna, tj. od hodnoty AP.getKrok(). Změnou velikosti kroku plátna se tak mění velikost kroku všech obrazců a všechny obrazce se při použití bezparametrických posunových metod posunou o stejnou vzdálenost. Mohli bychom to zavést i v naší třídě Strom. Při odstranění definice atributu krok a příslušných přístupových metod nesmíme samozřejmě zapomenout na odpovídající změnu definice testovací třídy. V té budeme muset použít místo atributu krok volání AP.getKrok(). Úpravy posunových metod V naší třídě definujeme způsob, jakým se instance přesouvá, na třech různých místech: v metodě setPozice(int,int), posunVpravo(int) a posunDolů(int). Již několikrát jsem říkal, že není dobré definovat stejnou činnost na několika místech. Pokud bychom se později rozhodli, že náš strom vylepšíme (např. necháme v koruně nakreslit pár zralých jablek), museli bychom algoritmus posunu opravit na třech místech. Z hlediska snazší spravovatelnosti kódu by proto bylo lepší, kdybychom chování metod pro posun vpravo a dolů odvodili od metody setPozice(int,int). Metodu posunVpravo(int) bychom tak upravili do tvaru: 1 public void posunVpravo( int vzdálenost ) 2 { 3 setPozice( getX()+vzdálenost, getY() ); 4 } Zefektivnění přesunu V dokumentaci třídy AktivníPlátno si můžeme přečíst: @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 262 z 433 Kapitola 6: Rozhraní 263 Efektivita vykreslování Efektivitu vykreslování je možné ovlivnit voláním metody nekresli(), která pozastaví překreslování plátna po nahlášených změnách. Její volání je výhodné např. v situaci, kdy je třeba vykreslit obrazec složený z řady menších obrazců a bylo by nevhodné překreslovat plátno po vykreslení každého z nich. V takovém případě totiž nejde jenom o to, že se plátno zbytečně mockrát překresluje, ale také o to, že při takovémto překreslování obrázek často nepříjemně bliká, protože se na plátně objevují části obrazců, která vzápětí jiný obrazec překryje. Proto je výhodné v průběhu tvorby obrazce překreslování pozastavit a nechat plátno překreslit až v okamžiku, kdy je nový vzhled obrazce definován. Do původního, tj. kreslícího stavu převedeme plátno voláním metody vraťKresli(), která vrátí vykreslování do stavu před posledním voláním metody nekresli(). Nemůžeme totiž plátnu nařídit, aby se hned začalo kreslit, protože nevíme, jestli vykreslován našeho obrazce není součástí nějakého většího vkreslování, pro které již bylo překreslování plátna pozastaveno. Kdybychom překreslování nyní zapnuli, zapnuli bychom je možná uprostřed onoho většího překreslování. Proto plátno pouze žádáme, aby se vrátilo do toho kreslícího stavu, ve kterém bylo v okamžiku, kdy jsme je naposledy žádali o to, aby se přestalo překreslovat. Nemůže se tedy stát, že by se při zavolání metody nekresli() v situaci, kdy je již vykreslování pozastaveno, začalo po následném zavolání vraťKresli() hned vykreslovat. Po dvou voláních nekresli() se začne vykreslovat až po dvou zavoláních vraťKresli(). Chceme-li proto zvýšit efektnost přesunových operací a zamezit tomu, aby bylo na pomalejších počítačích poznat, že se u stromu přesouvá nejprve koruna a teprve pak kmen, měli bychom upravit metodu setPozice() tak, aby nejprve zakázala překreslování, pak změnila nastavení koruny a kmene a pak opět překreslování zapnula (teď se nám hodí, že jsme algoritmu pro veškeré přesuny koncentrovali do jediné metody, kterou ostatní volají). Po úpravě by metoda mohla vypadat následovně: public void setPozice(int x, int y) { AP.nekresli(); koruna.setPozice( x, y ); kmen .setPozice( x + (koruna.getŠířka() - kmen.getŠířka()) / 2, y + koruna.getVýška() ); @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 263 z 433 264 Myslíme objektově v jazyku Java 1.5 } AP.vraťKresli(); V předchozí metodě jsem chtěl zdůraznit, že se dvojice metod ovlivňujících vykreslování chová jako závorky a proto jsem příkazy mezi nimi odsadil. To, jestli budete používat podobné zvýraznění i ve svých programech, ponechám na vašem vkusu. Podobu tříd Strom a StromTest ve stavu, do kterého jsme je právě doprovodili najdete opět v projektu 06_Rozhraní_Z ve třídách Strom_6b a Strom_6bTest. Zkuste obdobným způsobem přizpůsobit novým podmínkám definovaným v projektu 06_Rozhraní_A i vaší vlastní třídu. 6.7 Implementace několika rozhraní Třída může implementovat libovolný počet rozhraní současně. Deklarují-li přitom dvě rozhraní metodu se stejnou hlavičkou, stačí, když třída danou metodu implementuje jen jednou. No, stačí je špatné slovo – ono to totiž ani jinak nejde. Implementaci druhého rozhraní si nebudeme ukazovat na nějakém konkrétním projektu (na ten dojde za chvíli), ale předvedeme si ji na obyčejném pomocném rozhraní, které nazveme ISmazat (to abychom věděli, co pak s ním). Toto rozhraní bude deklarováno následovně: 1 public interface ISmazat 2 { 3 void nakresli( java.awt.Graphics2D kreslítko ) 4 void smazat() 5 }//public interface ISmazat Otevřete nyní zdrojový kód třídy Strom tak, abyste viděli na její hlavičku a natáhněte od ní implementační šipku k rozhraní ISmazat. Hlavička nyní dostala tvar public class Strom implements IKreslený, ISmazat @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 264 z 433 Kapitola 6: Rozhraní 265 Pokusme se nyní třídu Strom přeložit. Překladač se vzbouří a vypíše známé hlášení Strom is not abstract and does not override abstract method smazat() in ISmazat Definujte proto potřebnou metodu s prázdným tělem (stejně ji budeme za chvíli mazat, takže ji můžete pro tu chvíli napsat hned za hlavičku): public void smazat() {} Nyní by již měl překlad proběhnout bez námitek (leda byste někde udělali nějakou jinou chybu). Zkusme nyní přidat do projektu další rozhraní – např. ISmazat2. Abychom měli život jednodušší, už v něm žádnou metodu deklarovat nebudeme a pouze k němu od stromu natáhneme implementační šipku. V hlavičce třídy Strom přibude další položka v seznamu implementovaných rozhraní, ale jinak se nic nezmění. Rozhraní po třídě nechce nic, co by neuměla, takže její překlad proběhne opět bez problémů. Stejně by to dopadlo i tehdy, pokud by rozhraní deklarovalo některou z metod, které Strom již dávno implementuje. Vrátím-li se opět k naší analogii, tak bychom mohli říci, že třídě nic nebrání v tom, zařídit svým instancím několik licencí na různé činnosti. Bude-li nějaká licence vyžadovat např. přítomnost robota-metody horolezce, který bude umět vylézt do zadaného patra, pak není důvod, proč byl jeden a týž robot nemohl být použit při všech obdobných horolezeckých pracech nezávisle na tom, pod jakou licencí je aktuální činnost právě provozována. Odvolání implementace rozhraní Někdy se stane, že si v průběhu doby svůj původní záměr rozmyslíme a rozhodneme se implementaci rozhraní třídou odvolat. To, že k tomu stačí smazat příslušnou klauzuli v hlavičce je vám nejspíš jasné. Zkuste např. smazat informaci o tom, že Strom implementuje ISmazat a uvidíte, jak po překladu třídy zmizí i příslušná implementační šipka. Implementační šipky ale můžete zrušit i přímo v diagramu tříd aniž byste kvůli tomu museli otevírat zdrojový kód třídy. Stačí na šipku klepnout pravým tlačítkem myši. Tím šipka „ztloustne“ (to abyste věděli, kterou jste vybrali) a zároveň rozbalíte místní nabídku s jediným příkazem: Odstranit (viz obr. 6.11). Zadejte jej a šipka zmizí. Současně zmizí i implementační klauzule z hlavičky třídy. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 265 z 433 266 Myslíme objektově v jazyku Java 1.5 Obrázek 6.11 Zrušení implementace v diagramu tříd Před dalším čtením odstraňte z definice třídy Strom definici metody smazat() a současně odstraňte i obě pomocná „mazací“ rozhraní. 6.8 Návrhový vzor Služebník (Servant) Naše dosavadní tvorba dalších tvarů nebyla příliš efektivní. Když jsme navrhli nějaký nový tvar (jak strom, o kterém si pořád vyprávíme, tak vámi definované tvary), tak jsme v příslušné třídě definovali také všechny metody pro manipulaci s tímto objektem. Přitom tyto metody byly pro většinu objektů téměř stejné – často stačilo vzít jejich definice z jiné třídy a do právě definované třídy je zkopírovat. Mezi důležité programátorské zásady patří vyvarovat se kopírování kódu. Kopie kódu totiž přinášejí obdobné problémy jako magické hodnoty (literály), o nichž jsem hovořil v kapitole Konstanty a literály na straně 143. Když totiž později zjistíte, že jste něco naprogramovali špatně a nebo se změní zadání a je potřeba @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 266 z 433 Kapitola 6: Rozhraní 267 kód změnit, musíte projít všechna místa s tímto kódem a všude zanést stejnou opravu, což je potenciální zdroj nepříjemných chyb. Opakování částí kódu se dá zamezit různými metodami, s nimiž vás budu postupně seznamovat. Nyní si ukážeme jednu z nich, kterou je definice služebníka. Služebník je třída, jejíž instance (případně i ona sama) poskytují metody, které si vezmou potřebnou činnost (službu) na starost, přičemž objekty, s nimiž (nebo pro něž) danou činnost vykonávají, přebírají jako parametry. Návrhový vzor služebník je další ze vzorů, které jsou tak primitivní, že se do knihy GoF nedostaly. Jak jsem již říkal, začátečnické učebnice o návrhových vzorech nehovoří a pro pokročilé programátory je tato konstrukce příliš primitivní na to, aby jí dávali jméno. Chtěl-li jsem se na tento vzor v učebnici dále odvolávat, musel jsem mu jméno vymyslet sám. Proč rozhraní Toho, aby byla jedna metoda schopna zpracovávat parametry různých typů, můžeme dosáhnout dvěma způsoby: definicí několika přetížených verzí dané metody, definicí rozhraní, které deklaruje vlastnosti objektu potřebné pro to, aby metoda mohla splnit svůj úkol. První řešení, tj. definici sady přetížených metod, můžeme použít pouze tehdy, víme-li dopředu, že počet typů parametrů, které má metoda zpracovávat, je dopředu známý a relativně malý. Druhé řešení využívající definice rozhraní je mnohem univerzálnější, protože nijak dopředu neomezuje počet datových typů, pro jejichž instance bude možné danou metodu použít. Kdykoliv budeme v budoucnu potřebovat zahrnout mezi „obhospodařované“ datové typy další třídu, stačí, aby implementovala příslušné rozhraní a její instance mohou začít vystupovat jako parametry příslušných metod. Výhodnosti druhého řešení napomáhá i to, že třída může implementovat libovolný počet rozhraní současně, takže nic nebrání tomu, abychom pro různé účely definovali různé služebníky a třída pak implementovala rozhraní všech služebníků, jejichž služby chce využívat. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 267 z 433 268 Myslíme objektově v jazyku Java 1.5 S tímto návrhovým vzorem jsme se již seznámil v praxi – naše třída AktivníPlátno není nic jiného, než služebník vyžadující, aby objekty, které chtějí využívat jeho služeb, implementovaly rozhraní IKreslený. Teď si představte, že by pro zařazování nových objektů mezi spravované byly použity přetížené metody. To bychom pak byli už jednou pro vždy odkázáni na kreslení trojúhelníků, obdélníků a elips. Tak, jako jsme nyní přidali mezi kreslitelné objekty i čáry a texty, můžeme v budoucnu přidat i řadu dalších tvarů – stačí, když budou implementovat rozhraní IKreslený. Implementace Má-li metoda pracovat s nějakým objektem, většinou potřebuje, aby daný objekt něco uměl (např. aktivní plátno požaduje po objektech, které se hlásí do jeho správy, aby se uměly nakreslit dodaným kreslítkem). 1. Prvním krokem při návrhu služebníka je analýza toho, co má mít na starosti. Musíme si ujasnit, jaké metody musí služebník definovat a co budou tyto metody potřebovat od obsluhovaného parametru. Jinými slovy: co bude muset obsluhovaný parametry umět, aby metody mohly bezpečně splnit svůj úkol. 2. Druhým krokem je definice rozhraní, které deklaruje požadované vlastnosti obsluhovaného parametru, tj. vyjmenovává, na jaké zprávy bude muset umět reagovat (jinými slovy: jaké metody musí implementovat). Bude-li chtít nějaká instance využít služeb metod služebníka, bude muset implementovat toto rozhraní. 3. Třetím krokem je definice testů, které prověří, jestli následně definované metody služebníka dělají opravdu to, co mají. 4. Čtvrtým krokem je definice příslušného služebníka (a pokud možno jeho otestování). 5. Pátým krokem je implementace definovaného rozhraní obsluhovanými třídami, tj. třídami, s jejichž instancemi mají metody služebníka a/nebo jeho instancí pracovat (a opět jejich otestování). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 268 z 433 Kapitola 6: Rozhraní 269 Aplikace na náš projekt Ukážeme si, jak lze zařídit, aby se naše grafické objekty mohly plynule pohybovat po plátně aniž bychom museli v jejich třídách definovat metody, které tento pohyb realizují. Představte si, že bychom měli k dispozici třídu Přesouvač (ta je tu jako služebník), které bychom vždy předali objekt a ona by jej plynule přesunula do požadované cílové pozice. Aby to mohla učinit, museli bychom jí ale předat objekt, který je takového přesunu schopen. Požadavek na přemístění objektu můžeme obecně zadat dvěma způsoby; záleží na tom, zda jej potřebujeme přesunout na zadané souřadnice, nebo zda jej potřebujeme odsunout o požadovanou vzdálenost. V podstatě však jde o stejný problém, protože kdybychom uměli zjistit současné souřadnice objektu, mohli bychom jednu metodu realizovat pomocí druhé: posun o zadanou vzdálenost můžeme řešit tak, že ze současných souřadnic a požadovaného posunu spočteme nové souřadnice a na ty objekt přemístíme, posun na zadané souřadnice můžeme realizovat tak, že spočteme vzdálenost současných a cílových souřadnic a o tu pak objekt posuneme. Z předchozích úvah tedy vyplývá, že k tomu, abychom mohli objekt přesouvat, potřebujeme, aby měl implementované metody pro zjištění a nastavení pozice. Definujeme proto rozhraní IPosuvný s následujícími metodami: getX(), která vrátí aktuální x-ovou souřadnici objektu, getY(), která vrátí aktuální y-ovou souřadnici objektu, setPozice(int,int), která přesune objekt na zadanou pozici. Jeho definice (bez komentářů) by mohla vypadat např. následovně: 1 public interface IPosuvný 2 { 3 public int getX(); 4 public int getY(); public void setPozice(int x, int y); 5 6 }//public interface IPosuvný Zkopírujte si z projektu 06_Rozhraní_Z zdrojové kódy tříd Přesouvač a IPosuvný. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 269 z 433 270 Myslíme objektově v jazyku Java 1.5 Šťouralům dopředu přiznávám, že se třída Přesouvač nechová zcela korektně, protože u parametrů svých metod skrytě předpokládá, že implementují rozhraní IKreslený a nejsou-li zobrazeny na plátně, tak je na ně nechá zobrazit. V příští kapitole si ukážeme, jak tuto funkčnost řešit korektnějšími metodami. Implementace tohoto rozhraní našimi třídami není problém, protože všechny v úvahu přicházející třídy v projektu (včetně našeho stromu a vašeho paralelně vytvářeného obrazce) potřebné metody implementují. Nemusíme proto nic doprogramovávat, stačí pouze natáhnout implementační šipky od každé „posuvné“ třídy k implementovanému rozhraní (viz obr. 6.12). Podíváte-li se nyní na hlavičku kterékoliv z tříd, od nichž jste natáhli druhou implementační šipku k rozhraní IPosuvný, uvidíte, že hlavička přihlašuje třídu k implementaci dvou rozhraní – např. hlavička třídy Strom bude mít tvar: public class Strom implements IKreslený, IPosuvný Závěrečný test Nyní už stačí pouze připravit text, abychom ověřili, že všechno funguje jak má. Postupujte následovně: 1. Nejsou-li ještě všechny třídy přeloženy, přeložte projekt. 2. V místní nabídce třídy StromTest zadejte Vytvořit testovací metodu. 3. Pomocí bezparametrického konstruktoru vytvořte instanci třídy Přesouvač a nazvěte je přes1. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 270 z 433 Kapitola 6: Rozhraní 271 Obrázek 6.12 Projekt po přidání třídy Přesouvač a rozhraní IPosuvný a po natažení příslušných implementačních šipek 4. Pošlete tomuto přesouvači zprávu přesunO(objekt,doprava,dolů), kde jako objekt zadáte instanci strom3 a zbylé dva parametry nastavíte na -100. Přesouvač přesune strom z pravého dolního rohu do levého horního rohu. 5. Pomocí konstruktoru s celočíselným parametrem vytvořte přesouvač s rychlostí 5 a nazvěte jej přes5. 6. Pošlete tomuto přesouvači zprávu přesunO(objekt,doprava,dolů), kde jako objekt zadáte instanci strom1 a necháte jej přesunout o 200 bodů vpravo a 0 bodů dolů. 7. Přesouvači přes5 pošlete ještě zprávu přesunNa(objekt,x,y), kde jako objekt zadáte instanci strom4, kterou necháte přesunout na souřadnice x=200 a y=150. 8. Ukončete generování testu. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 271 z 433 272 Myslíme objektově v jazyku Java 1.5 Definujte třídu Pšouk s dvěma konstruktory: bezparametrickým a jednoparametrickým s celočíselným parametrem síla. Vyvolání bezparametrického konstruktoru bude mít stejný efekt, jako vyvolání jednoparametrického konstruktoru s parametrem rovným jedné. Ve třídě definujte metodu přifoukni(IPosuvný), která objekt předaný jako parametr třikrát zvětší o počet bodů rovný síle dané instance a metodu ufoukni(IPosuvný), která svůj parametr stejným způsobem zmenší. Aby jednotlivé kroky zvětšení nebo zmenšení nenásledovaly moc rychle za sebou, zavolejte mezi nimi metodu P.čekej(int), které zadáte počet milisekund, které má počkat před tím, než program pustí dál. Vymyslete, jak musí být definováno rozhraní INafukovací, a by instance třídy Pšouk mohly svůj úkol splnit, a definujte je. Definujte třídu PšoukTest, která prověří funkčnost vámi definované třídy. Tentokrát vám již vzorové řešení ukazovat nebudu, protože se domnívám, že už tak velkou berličku nepotřebujete. Najdete je však (podle očekávání) v projektu 06_Rozhraní_Z. Vedle očekávané třídy Pšouk a rozhraní INafukovací zde najdete také třídu Kompresor, jejíž instance umějí předaný objekt typu INafukovací plynule zvětšit, resp. zmenšit o nebo na libovolnou zadanou velikost. 6.9 Refaktorování V průběhu vývoje programu se velmi často dostaneme do situace, ve které zjistíme, že program není pro naše účely optimálně navržen. Nejde o to, aby program dělal něco jiného. Stále chceme, aby dělal přesně to, co dělá doposud. Jenom by se nám hodilo, kdybychom měli program navržený trochu jinak. Ve všech učebnicích návrhu programových systémů se dozvíte, že není moudré začít programovat hned po obdržení zadání, ale že je nanejvýš nutné nejprve celý problém důkladně zanalyzovat, abychom se pak při kódování (tj. při zápisu navrženého programu) pokud možno vystříhali slepých uliček a nemuseli zbytečně velké části programu předělávat. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 272 z 433 Kapitola 6: Rozhraní 273 Zkušenost ukazuje, že dobře provedená analýza výrazně zrychluje a zefektivňuje celý projekt, nicméně nezávisle na důkladnosti předběžné analýzy se prakticky vždy objeví situace, kdy je třeba v původním návrhu něco změnit. Vedle rozsáhlých výzkumů jak zadaný problém co nejlépe zanalyzovat a připravit pro následné kódování proto začali počítačoví vědci bádat i nad tím, jak v případě, kdy se přes veškerou předchozí snahu ukáže, že je třeba návrh programu změnit, provést tuto změnu s co nejmenším úsilím a co nejlepším výsledným efektem1. Změny kódu, jejichž úkolem je zlepšit návrh programu, avšak zachovat veškerou jeho stávající funkčnost, jsou označovány jako refaktorování (anglicky refactoring). Pokroky ve výzkumu jeho možností a standardizace refaktorovacích postupů mají dva viditelné důsledky: Programátoři se přestali bát měnit hotový kód, protože se naučili postupy, jak to dělat rychle, bezpečně a levně. Řada standardizovaných refaktorovacích postupů se stala součástí základní výbavy profesionálních vývojových prostředí. Tím se refaktorování stalo ještě snadnější, levnější a bezpečnější. Refaktorace spočívá v postupné aplikaci série drobných změn. Každé z těchto změn musí být tak drobná, aby bylo na první pohled jasné, že se chování systému v důsledku provedené úpravy nezmění. Potřebujeme-li udělat nějakou větší změnu, rozložíme ji nejprve na sérii jednoduchých změn a ty pak provedeme jednu po druhé. (Někdy to nejde, ale měli byste se o to vždy pokusit.) To ale nestačí – po každé změně je totiž potřeba celý kód znovu otestovat. Jsme totiž lidé chybující a i při provádění naprosto přehledných úprav dokážeme udělat chybu. Kdybychom netestovali po každém jednoduchém kroku, po kterém ještě přesně víme, co jsme v systému změnili, bylo by hledání případné chyby příliš obtížné. Ukázka Ukažme si jednu takovou jednodušší situaci na příkladu. V naší třídě Strom jsme doposud definovali metodu setPozice(int, int), ale nedefinovali jsme metodu setRozměr(int,int). Důvodem pro tuto absenci bylo to, že instance třídy Strom se 1 Filozofii refaktorování, jeho principy, metody a nejdůležitější vzory jsou skvěle popsány v knize Fowler M.: Refactoring: Improving the Design of Existing Code, Addison Wesley, 2002, která vyšla v českém překladu pod názvem Refactoring – Zlepšení existujícího kódu, Grada, 2003, ISBN 80-247-0299-1. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 273 z 433 274 Myslíme objektově v jazyku Java 1.5 konstruovaly s nastaveným poměrem šířky, resp. výšky kmene ku šířce, resp. výšce celého stromu. Tyto poměry bychom se sice mohli pokusit získat dotazem na aktuální velikost koruny a kmene a následným výpočtem, ale vzhledem k zaokrouhlovacím chybám bychom v některých situacích nemuseli obdržet původně zadané hodnoty1, takže když bychom strom zmenšili a opět zvětšili, mohl by pak vypadat jinak. Pokusíme se proto definovat metodu setRozměr(int,int) nyní. Při té příležitosti si ukážeme, jak lze v podobných případech postupovat. Znovu bych zopakoval: postupuje se ve velmi malých krocích a po každém kroku je třeba upravení program hned otestovat. Přiznám se, že jsem si tuto definici schválně chovával až sem, protože díky aktivnímu plátnu se nám nebudou jednotlivé objekty odmazávat a navíc si můžeme na plátně natáhnout síť čar, které nám umožní lépe kontrolovat, že nově nastavené rozměry odpovídají našim požadavkům. 1. krok: Vytvoření testu Jdeme na to. Jak jsme si v pasáži TDD – vývoj řízený testy na straně 104 řekli, TDD vyžaduje, abychom nejprve napsali testy a teprve pak testovaný program. V prvním kroku bychom měli připravit testy. Ve třídě Strom proto definujeme prázdnou metodu setRozměr(int,int) (tu vám snad ukazovat nemusím) a v testovací třídě StromTest pak metodu testSetRozměr(). Aby byl celý postup opravdu průzračný, definuji testovací metody „ručně“: 1 public void testSetRozměr() 2 { ap.setKrokRozměr(10, 30, 30); 3 assertEquals(true, P.souhlas( 4 5 "Strom 1: x=0, y= 0, š=100, v=150, šSt/šKm=10, vSt/vKo=3\n" "Strom 2: x=0, y=150, š=100, v=150, šSt/šKm= 5, vSt/vKo=4\n" 6 7 "Strom 3: x=100, y=100, š=200, v=200, šSt/šKm=20, vSt/vKo=2\n" "Strom 4: x=100, y=0, š=150, v=100, šSt/šKm= 3, vSt/vKo=5\n" 8 9 "\nPozice, rozměry a poměry kmen a korun souhlasí?")); 10 strom1.setRozměr( 50, 30 ); 11 strom2.setRozměr( 50, 40 ); 12 strom3.setRozměr( 40, 40 ); 13 strom4.setRozměr( 30, 50 ); 14 assertTrue( P.souhlas( 1 + + + + Pokud by byl při konstrukci stromu nastaven např. poměr výšky 3 a celková velikost stromu 8, měla by mít koruna velikost 6 a kmen 2. Když bychom pak chtěli změnit velikost stromu příště, spočetli bychom z velikosti koruny a kmen poměr výšky 4. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 274 z 433 Kapitola 6: Rozhraní 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 } 275 "Strom_5 1: x=0, y= 0, š=50, v=30, vSt/vKo=3, šSt/šKm=10\n" "Strom_5 2: x=0, y=150, š=50, v=40, vSt/vKo=4, šSt/šKm= 5\n" "Strom_5 3: x=100, y=150, š=40, v=40, vSt/vKo=2, šSt/šKm=20\n" "Strom_5 4: x=100, y=0, š=30, v=50, vSt/vKo=5, šSt/šKm= 3\n" "\nPozice, rozměry a poměry kmen a korun souhlasí?")); strom1.setRozměr( 100, 150 ); strom2.setRozměr( 100, 150 ); strom3.setRozměr( 200, 200 ); strom4.setRozměr( 150, 100 ); assertTrue( P.souhlas( "Strom 1: x=0, y= 0, š=100, v=150, šSt/šKm=10, vSt/vKo=3\n" "Strom 2: x=0, y=150, š=100, v=150, šSt/šKm= 5, vSt/vKo=4\n" "Strom 3: x=100, y=100, š=200, v=200, šSt/šKm=20, vSt/vKo=2\n" "Strom 4: x=100, y=0, š=150, v=100, šSt/šKm= 3, vSt/vKo=5\n" "\nPozice, rozměry a poměry kmen a korun souhlasí?")); + + + + + + + + Metoda nejprve přepne plátno na jemnější krok, abychom mohli zkontrolovat správné zobrazení malých instancí a zobrazí dialogové okno oznamující rozměry stromů z přípravku. Když je odsouhlasíte, nastaví pro každý ze stromů rozměry, které nejsou soudělné s jeho velikostí ani poměry výšky a šířky kmene ku celkové výšce či šířce. Po dalším odsouhlasením se pokusí uvést vše do původního stavu, který opět nechá odsouhlasit. 2. krok: Definice nových atributů Prvním krokem by měla být vždy příprava testu. Názvy dalších kroků se ale budou při řešení různých úloh různit. Neberte je proto jako dogma, ale spíš jako vodítko, pro lepší orientaci v popsaném postupu. Prozatím se při testech se stromy vytvořenými přípravkem následně nic neděje, protože tělo metody setRozměr(int,int) je prázdné. Zatím je ještě nemůžeme naplnit, protože nemám dostatečné podklady pro tvorbu stromů. Ty si zatím nechává konstruktor při konstrukci stromu pro sebe. Když se zamyslíte nad tím, jak metodu setRozměr(int,int) definovat, jistě vás napadne, že potřebný kód je již definován v konstruktoru. Ten ale při vytváření instancí koruny a kmene používá informace o vzájemných poměrech šířky a výšky kmene a celého stromu, které se nikde neuchovávají. V dalším kroku proto definujeme oba poměry jako atributy instance, které necháme konstruktorem nastavit. Po úpravě program ihned znovu otestujeme, i když se nám nezdá, že bychom měli takovouto změnou běh programu ovlivnit. Úpravy (jsou vysazeny tučně) si můžete prohlédnout v následujícím výpisu: @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 275 z 433 276 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 Myslíme objektově v jazyku Java 1.5 private int podílVýškyKmene; private int podílŠířkyKmene; public Strom_5( int x, int y, int šířka, int výška, int podílVýškyKmene, int podílŠířkyKmene ) { this.podílVýškyKmene = podílVýškyKmene; this.podílŠířkyKmene = podílŠířkyKmene; int výškaKmene = výška / podílVýškyKmene; int výškaKoruny = výška - výškaKmene; int šířkaKmene = šířka / podílŠířkyKmene; int posunKmene = ( šířka - šířkaKmene) / 2; koruna = new Elipsa ( x, y, šířka, výškaKoruny, Barva.ZELENÁ ); kmen = new Obdélník( x+posunKmene, y+výškaKoruny, šířkaKmene, výškaKmene, Barva.ČERVENÁ ); } I po tak malé opravě bychom měli nechat třídu znovu přeložit, abychom se ubezpečili, že jsme opravu provedli správně. Funkce konstruktoru se touto opravou nijak nezměnila, nicméně bychom ji měli pro jistotu otestovat spuštěním nějakého testu, který ji prověří – stačí např. natáhnout přípravek do zásobníku odkazů. 3. krok: Kopírování těla konstruktoru do těla metody Potřebné informace máme k dispozici, takže můžeme vytvořit tělo metody setRozměr. Zkopírujeme do ní nejprve tělo konstruktoru. Hned si ale někam poznamenáme, že máme stejný kód na dvou místech a že tento problém budeme muset v budoucnu ještě řešit. 1 public void setRozměr(int šířka, int výška) 2 { int výškaKmene = výška / podílVýškyKmene; 3 int výškaKoruny = výška - výškaKmene; 4 int šířkaKmene = šířka / podílŠířkyKmene; 5 int posunKmene = ( šířka - šířkaKmene) / 2; 6 AP.nekresli(); 7 koruna = new Elipsa ( x, y, šířka, výškaKoruny, Barva.ZELENÁ ); 8 kmen = new Obdélník( x+posunKmene, y+výškaKoruny, 9 šířkaKmene, výškaKmene, Barva.ČERVENÁ ); 10 AP.vraťKresli(); 11 12 } Nad zkopírovaným programem nebudeme nijak bádat a požádáme rovnou překladač, aby jej přeložil. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 276 z 433 Kapitola 6: Rozhraní 277 4. krok: Dočasné „odkonstatnění“ některých atributů Tentokrát se nám nepodaří program ani přeložit. Překladač nám totiž oznámí, že nemůže přiřadit hodnotu konstantě koruna. Toho se můžeme na chvilku zbavit tím, že z deklarace atributu koruna (a hned také i z deklarace atributu kmen) odstraníme modifikátor final. Musíme si to ale někam zapsat, abychom je tam po zprovoznění překladu nezapomněli vrátit. 5. krok: Definice potřebných lokálních proměnných Při dalším pokusu o překlad se dozvíme, že používáme proměnnou x, kterou jsme nedeklarovali. Víme také proč: konstruktor dostal příslušné hodnoty jako parametr. Tentokrát je však nebudeme deklarovat jako atributy, protože víme, že příslušné hodnoty můžeme získat pomocí přístupových metod getX() a gwetY(). Definujeme proto lokální proměnné x a y a inicializujeme je pomocí těchto metod. 1 public void setRozměr(int šířka, int výška) 2 { int x = getX(); 3 int y = getY(); 4 5 int výškaKmene = výška / podílVýškyKmene; int výškaKoruny = výška - výškaKmene; 6 int šířkaKmene = šířka / podílŠířkyKmene; 7 8 int posunKmene = ( šířka - šířkaKmene) / 2; AP.nekresli(); 9 10 koruna = new Elipsa ( x, y, šířka, výškaKoruny, Barva.ZELENÁ ); kmen = new Obdélník( x+posunKmene, y+výškaKoruny, 11 12 šířkaKmene, výškaKmene, Barva.ČERVENÁ ); AP.vraťKresli(); 13 14 } 6. krok: Odstranění tvorby nových instancí koruny a kmene Nyní nám již překladač náš kód přeloží. Vyzkoušíme test – a ejhle, i ten projde. Pustíme se tedy do našeho restu, kterým je zbytečné vytváření nových instancí koruny a kmene. Místo toho, abychom vytvářeli novou korunu a kmen by mělo stačit, abychom změnili rozměr dosavadní koruny a kmene. Jak vidíme z programu, u kmene pak musíme vedle jeho rozměru změnit i jeho pozici. 1 public void setRozměr(int šířka, int výška) 2 { int x = getX(); 3 int y = getY(); 4 int výškaKmene = výška / podílVýškyKmene; 5 6 int výškaKoruny = výška - výškaKmene; @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 277 z 433 278 7 8 9 10 11 12 13 14 } Myslíme objektově v jazyku Java 1.5 int šířkaKmene = šířka / podílŠířkyKmene; int posunKmene = ( šířka - šířkaKmene) / 2; AP.nekresli(); koruna.setRozměr( šířka, výškaKoruny ); kmen.setPozice( x+posunKmene, y+výškaKoruny ); kmen.setRozměr( šířkaKmene, výškaKmene ); AP.vraťKresli(); 7. krok: Vrácení koruny a kmene mezi konstanty Testy stále fungují, takže se můžeme pustit do dalšího kroku, kterým je opětné vrácení atributů koruna a kmen mezi konstanty. 8. krok: Vyvolání metody setRozměr(int,int) v konstruktoru Pořád nám ještě zbývá jeden poznamenaný problém, kterým je zdvojení stejného kódu v konstruktoru a v metodě setRozměr(int,int). V metodě kód potřebujeme, protože bychom jinak nedovedli splnit její poslání. Upravit ale můžeme konstruktor. Když je potřebný kód definován v těle metody, proč jej tedy nezavolat. Budu se stále držet zásady, že kroky mají být opravdu jednoduché a do konstruktoru proto pouze přidám volání metody setRozměr(int,int). Možná vám tento krok bude připadat zbytečně malý, ale to nevadí. Nebojte se dělat malé kroky (a hned je otestovat). Zvýší to vaši jistotu ve stabilitu vytvářeného kódu. 1 public Strom_6c( int x, int y, int šířka, int výška, int podílŠířkyKmene, int podílVýškyKmene ) 2 3 { this.podílVýškyKmene = podílVýškyKmene; 4 5 this.podílŠířkyKmene = podílŠířkyKmene; int výškaKmene = výška / podílVýškyKmene; 6 7 int výškaKoruny = výška - výškaKmene; 8 int šířkaKmene = šířka / podílŠířkyKmene; int posunKmene = ( šířka - šířkaKmene) / 2; 9 10 AP.nekresli(); koruna = new Elipsa ( x, y, šířka, výškaKoruny, Barva.ZELENÁ ); 11 12 kmen = new Obdélník( x+posunKmene, y+výškaKoruny, 13 šířkaKmene, výškaKmene, Barva.ČERVENÁ ); setRozměr( šířka, výška ); 14 15 AP.vraťKresli(); 16 } 9. krok: Odstranění zdvojeného kódu z konstruktoru Testy stále chodí, tak nemusíme přemýšlet o tom, jestli je vše v pořádku. Když chodí testy, je to v pořádku (jsou-li dobře napsané). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 278 z 433 Kapitola 6: Rozhraní 279 Podíváme se tedy do konstruktoru, co můžeme vyhodit. Začal bych ale raději tím, co vyhodit nemůžeme, a tím je vytvoření instancí koruny a kmene a přiřazení odkazů na ně do příslušných atributů. Při vytváření těchto instancí však používám řadu pomocných proměnných, jejichž hodnoty jsme před tím počítali. Protože ale víme, že rozměr těchto instancí vzápětí nastavíme, stačí, když je při vytvoření pouze správně umístíme. Navíc stačí správně umístit pouze korunu, protože pozice kmene se stejně odvozuje z pozice a rozměru koruny. Umístíme proto korunu do správné pozice, ale přiřadíme jí pro začátek velikost 1 bod. Totéž pak uděláme i se kmenem. Protože se přitom nic nekreslí (kreslení máme na doby tvorby stromu potlačeno), tak nás toto zdánlivě zbytečné dvojité umisťování koruny a kmene prakticky nezdrží. Po všech těchto úpravách tedy získáme následující podobu konstruktoru našeho stromu: 1 public Strom_6c( int x, int y, int šířka, int výška, 2 int podílŠířkyKmene, int podílVýškyKmene ) 3 { this.podílVýškyKmene = podílVýškyKmene; 4 this.podílŠířkyKmene = podílŠířkyKmene; 5 AP.nekresli(); 6 7 koruna = new Elipsa ( x, y, 1, 1, Barva.ZELENÁ ); kmen = new Obdélník( x, y, 1, 1, Barva.ČERVENÁ ); 8 9 setRozměr( šířka, výška ); AP.vraťKresli(); 10 11 } 10. krok: Doplnění metody setRozměr(Rozměr) Když jsme vítězně dokončili zavedení metody setRozměr(int,int), nic nám nebrání zavést také metodu setRozměr(Rozměr): 1 public void setRozměr(Rozměr rozměr) 2 { 3 setRozměr( rozměr.šířka, rozměr.výška ); 4 } 11. krok: Doplnění metody setOblast(Oblast) Posledním krokem celé operace bude přidání metody setOblast(Oblast). Tam bychom neměli zapomenout na to, že při akcích rozložených do několika kroků je výhodné vypnout překreslování plátna. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 279 z 433 280 Myslíme objektově v jazyku Java 1.5 1 public void setOblast( Oblast oblast ) 2 { AP.nekresli(); 3 setPozice( oblast.x, oblast.y ); 4 5 setRozměr( oblast.šířka, oblast.výška ); AP.vraťKresli(); 6 7 } Výslednou podobu třídy Strom i testovací třídy najdete v projektu 06_Rozhraní_Z v souborech Strom_6c a Strom6cTest. Nyní vám již nic nebrání zadat, že třída Strom implementuje rozhraní INafukovací a definovat test, který vyzkouší její spolupráci s instancemi třídy Kompresor. 6.10 Projekt Výtah Na závěr kapitoly si zkuste vyřešit další samostatný projekt. Zadání je následující: Definujte třídu Výtah, která má za úkol simulovat provoz výtahu. Instance třídy bude schopna jezdit nahoru a dolů v zadaném sloupci aktivního plátna. Na požádání bude umět přijet do zadaného patra, naložit pasažéra (grafický obrazec) a odvézt jej do požadovaného patra a tam jej vyložit do některého se sousedních sloupců. Prozatím se omezte na výtah pro jediného pasažéra. Zadání jsem se pokusil udělat jako rozumný kompromis mezi obecným (např. „simulujte provoz výtahu“) a konkrétním, popisujícím detailně jednotlivé požadavky. Zkuste si nejprve udělat analýzu toho, jaké by měla mít konstruovaná třída vlastnosti jaké by měla mít definované metody (tj. na jaké zprávy by měla umět reagovat) a pak čtěte dál a porovnejte si svůj přístup se vzorovým řešením. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 280 z 433 Kapitola 6: Rozhraní 281 Chcete-li mít před analýzou trochu jasnější představu o tom, co se po váš požaduje, zkopírujte si z projektu 06_Rozhraní_Z třídy VýtahX a VýtahXTest. Spuštěním testu obsluhy si můžete přehrát malou animaci, která by vám mohla naznačit, co po vás vlastně chci. Ti odvážnější se mohou pokusit hned definovat celou třídu Výtah i s potřebnými testy. Pro ty lenivější jsem připravil kostru třídy s předdefinovanými prázdnými metodami, které odvodíme v následující analýze. Můžete je proto ihned spustit aniž by testy začínaly poukazováním na to, že chcete použít neexistující metody. Těm netrpělivým bych znovu poradil: chcete-li se opravdu něco naučit, nenahlížejte do definic a zkuste podle následujícího textu nejprve definovat třídu sami Vzorové řešení použijte pouze v případě, když vám něco nebude chodit a nebudete si vědět rady. Analýza problému Před tím, než začneme programovat, bychom se měli zamyslet nad tím, co po třídě a jejích instancích budeme chtít. Na jaké zprávy mají umět reagovat a které metody by proto měly poskytovat. Okolí Náš výtah se bude pohybovat na aktivním plátně. Ze zadání víme, že výtah se bude pohybovat v jednom z jeho sloupců. Ten budeme považovat za výtahovou šachtu. Asi by bylo rozumné považovat za patra budovy jednotlivá řady políček. Aby byl výtah výtahem, musí být schopen něco vozit. Náš výtah bude vozit pasažéry, což budou grafické objekty. Protože se budou pasažéři s výtahem přesouvat (a dopředu tušíme, že je budeme přesouvat přesouvačem), měly by implementovat rozhraní IPosuvný. Kdykoliv budeme proto někde potřebovat deklarovat pasažéra (většinou to bude parametr), budeme deklarovat objekt typu IPosuvný. Pro zjednodušení budeme skrytě předpokládat, že pasažér je definován tak, že se vejde do jednoho políčka aktivního plátna a že jeho cílové souřadnice budeme moci nastavit jako souřadnice levého horního rohu políčka, do kterého jej budeme chtít přesunout. Konstruktory Třídu jsme tedy zasadili do jejího budoucího okolí. Podívejme se nyní na to, jak bychom měli vytvářet její instance. Navrhněme, jaké budeme potřebovat kon@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 281 z 433 282 Myslíme objektově v jazyku Java 1.5 struktory, co vše jim musíme říci, aby pro nás vytvořily potřebný výtah, a jaké základní vlastnosti by měl čerstvě zkonstruovaný výtah mít. Začnu od konce. Čerstvě zkonstruovaný výtah by se měl zobrazit jako čtverec zabírající právě jedno políčko aktivního plátna a měl by být umístěn v přízemí, tj. ve spodním políčku svého sloupce. Zkusíme definovat dva konstruktory: první bude nejjednodušší možný, druhý umožní nastavit některé další vlastnosti vytvářeného výtahu. Po přehrání výše zmíněné testovací animace bychom se asi mohli shodnout na tom, že i nejjednoduššímu konstruktoru bychom měli zadat alespoň sloupec, v němž bude výtah jezdit. Tento konstruktor by tak měl mít hlavičku public Výtah( int sloupec ) Pokusme se nyní odvodit, o co bychom měli doplnit rafinovanější konstruktor. Při shlédnutí animace vás jistě napadne, že by měl umožňovat nastavit barvu výtahu. Druhá věc, které jste si možná všimli, je, že každý z výtahů jezdil jinou rychlostí. Měli bychom proto „lepší“ konstruktor doplnit i možností nastavit rychlost výtahu. Raději si už dál nebudeme vymýšlet a dohodneme se, že jeho hlavička bude: public Výtah( int sloupec, int rychlost, Barva barva ) Potřebné metody Položme si nyní otázku: „Co musí výtah umět?“ a odvoďme z ní, na jaké zprávy by měl reagovat a jaké metody bychom proto měli definovat. Když chcete někam jet výtahem, musíte jej napřed přivolat. Aby k vám ale mohl dojet, musí vědět, ve kterém patře jste. Jednou z možností je předat ve zprávě budoucího pasažéra jako parametr, podle kterého výtah pozná, ve kterém patře se nachází. Ke splnění tohoto požadavku definujeme metodu public void přjeďK( IPosuvný pasažér ) Když výtah přijede, musí pasažér nastoupit. Rozšiřovat ale třídu pasažéra a schopnost nástupu do výtahu není optimální. Lepší bude, naučíme-li výtah nabrat nového pasažéra. Definujeme proto metodu public void nástup( IPosuvný pasažér ) Pasažér je tedy ve výtahu. Nyní bychom potřebovali, aby výtah uměl s pasažérem přijet do požadovaného patra. Definujeme proto metodu: public void doPatra( int patro ) Všimněte si, že této metodě již nepředáváme pasažéra jako parametr. Pasažér již nastoupil. Výtah jej nabral a měl by proto vědět, koho veze. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 282 z 433 Kapitola 6: Rozhraní 283 V cílové stanic by měl výtah vysadit svého pasažéra. Problém je, na kterou stranu jej budeme vysazovat. Na tu, ze které nastoupil? Můžeme jej ale také chat vybrat. Uděláme to šalamounsky tak, že doplníme třídu dvěma metodami“ jednou pro výstup vpravo a druhou pro výstup vlevo: public void výstupVpravo() public void výstupVlevo() Když jsme pěkně krok za krokem prošli celý postup převozu pasažéra z výchozí do cílové pozice, tak by asi bylo šikovné, kdybychom mohli celý proces realizovat jediným příkazem, po němž by výtah přijel pro pasažéra, naložil jej a odvezl do požadovaného patra, kde by jej také vyložil. K tomu mu stačí vědět, kdo jej žádá o převoz a kam touží dojet. Aby bylo možno vystupovat v cíli na obě strany, definujeme opět dvě metody: void odvezVpravo( IPosuvný pasažér, int patro ) void odvezVlevo ( IPosuvný pasažér, int patro ) Implementace Hrubou analýzu máme za sebou, takže máme základní představu o tom, co a jak by měla naše třída a její instance dělat. To ale neznamená, že přesně takový celý projekt na konci bude. V průběhu implementace se může ukázat, že některá naše rozhodnutí ve fázi analýzy nebyla optimální, protože jsme si neuvědomili některé skutečnosti, které nám dojdou až při implementaci. Musíte být proto připraveni na to, že všechno se může změnit. Takže vzhůru na implementaci! Abych vám ušetřil práci, připravil jsem vám v projektu 06_Rozhraní_Z třídy Výtah a VýtahTest, které obsahují předdefinované prázdné verze konstruktorů a výše popsaných metod. Obě třídy jsou navrženy tak, že jdou hned přeložit. V následujících řádcích vás pomalu provedu postupem, jak tuto třídu „rozchodit“. Nebudu ale již explicitně ukazovat, jak by se dalo to či ono naprogramovat, ale budu věci spíše naznačovat, abyste si je mohli naprogramovat sami. Octnete-li se náhodou ve stadiu zoufalství, kdy si nebudete vědět rady, můžete se podívat na vzorové řešení ve třídě VýtahX. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 283 z 433 284 Myslíme objektově v jazyku Java 1.5 Implementovaná rozhraní Nejprve bychom se měli zamyslet nad základní charakteristikou třídy. Víme, že třída má na animačním plátně simulovat výtah. Musí jej tedy umět zobrazit a k tomu nutně potřebuje implementovat rozhraní IKreslený. Výtah by se měl pohybovat, a to pokud možno plynule. K tomu bychom mohli využít přesouvače. Abychom mohli využít přesouvače, musí třída implementovat rozhraní IPosuvný. Doplňte hlavičku předpřipravené třídy Výtah o deklarace implementace potřebných rozhraní a doplňte (pro začátek alespoň prázdné) definice implementovaných metod a výslednou třídu přeložte. Atributy Nyní bychom se měli zamyslet nad potřebnými atributy. Na základě zkušeností se stromem a vašimi grafickými objekty vás jistě napadne, že instance potřebuje mít jako atribut odkaz na obrazec, který bude reprezentovat výtah. Tímto obrazcem by měl být obdélník, který je velký jako jedno pole aktivního plátna a bude na počátku umístěn při spodním okraji plátna. Otázkou je, jestli je výhodnější si sloupec, v němž se výtah nachází, pamatovat v nějakém konstantním atributu (konstantním, protože lze očekávat, že by se v průběhu života výtahu neměl měnit), anebo zda si jej vždy zjistíme od obdélníku představujícího výtah. Rozhodnutí nechám na vás – vyberte si, co je vám sympatičtější. Obdobné rozhodnutí vás čeká i v otázce patra, v němž se výtah zrovna nachází. I tady si můžete vybrat, jestli pro ně vyčleníte atribut (tentokrát již ne konstantní), nebo jestli se na ně vždy zeptáte výtahu. Dalším atributem výtahu by měl být asi přesouvač, kterým se výtah nechává elegantně přesouvat z aktuální pozice do pozice požadované a který může použít i pro přesun nastupujících a vystupujících osob. Určitě uznáte, že by asi nebylo moudré vytvářet pro každý požadovaný přesun nový přesouvač – výhodnější bude, když přesouvač bude vytvořen konstruktorem a instance pak při každém přesunu použije ten samý. Kdyby všechny výtahy jezdily stejně rychle, mohly by používat společný přesouvač, který by tak mohl být definován jako atribut třídy. Chceme-li však umožnit, aby každý výtah jezdil svojí vlastní rychlostí, musí mít každá instance svůj vlastní přesouvač. Doplňte definice konstruktorů tak, aby inicializovaly potřebné atributy a spuštěním testu Inicializace ověřte, že se výtahy správně nakreslí a umístí do výchozích pozic. Trochu vám napovím: aby se výtah ne plátně skutečně nakreslil, budete muset doplnit definici těla dříve definované metody nakresli(Graphics2D). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 284 z 433 Kapitola 6: Rozhraní 285 Postup při návrhu metod Konstruktory a atributy máme za sebou. To sice ještě neznamená, že časem nezjistíme, že svá rozhodnutí budeme muset změnit, ale prozatím je budeme považovat za konečná. Podívejme se proto na řadové metody. Při jejich implementaci nemusíme přesně dodržovat pořadí, v němž jsme je navrhli ve fázi analýzy. Rozumným hlediskem pro výběr příští definované metody je to, zda bude za současného stavu definic možné definici metody otestovat. Kromě toho je samozřejmě rozumné definovat nejdříve metody, o nichž se domníváme, že je budeme chtít použít v definici jiných metod. Seznam metod navržených při analýze začínala metodou přijeďK(IPosuvný). Při prohlídce zbývajících metod ale asi každého napadne, že v definici této metody by se nám mohla hodit metoda doPatra(int). Definujte tedy nejprve ji. Metoda doPatra(int) Metoda realizující příjezd do požadovaného patra přebírá cílové patro jako parametr. Její definice by měla být jednoduchá: Pamatujeme-li si v nějakém atributu patro, v němž se výtah právě nachází, stačí násobit rozdíl pater výškou patra (tj. velikostí kroku aktivního plátna) a o tento počet bodů přesunout obdélník představující výtah. Po dojetí do cíle je třeba si v atributu zapamatovat příslušné patro. Nemá-li výtah uloženo patro, v němž se nachází, zjistí si svislou pozici cílového patra a nechá se přesunout do této cílové pozice. Definujte tělo metody doPatra(int) a spuštěním testu PrázdnýVýtahDoPatra ověřte, že výtahy se pohybují požadovaným způsobem. Metoda přijeďK(IPosuvný) Když již umíme dojet s výtahem do požadovaného patra, mohli bychom jej naučit i přijet ke svému pasažérovi. K tomu stačí zjistit patro, v němž se nachází pasažér, a nechat výtah do tohoto patra dojet. Patro pasažéra zjistíme např. tak, že spočteme celočíselný podíl jeho svislé souřadnice a velikosti pole aktivního plátna. Tak se dozvíme řádek, v němž se nachází. Odečtením tohoto řádku zvednutého od počtu řádků plátna zjistíme podlaží. Výsledek musíme zmenšit o jedničku, protože přízemí je považováno za nulté podlaží. Definujte tělo metody přijeďK(IPosuvný) a spuštěním testu PřijeďK ověřte, že výtah skutečně přijede, kam má. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 285 z 433 286 Myslíme objektově v jazyku Java 1.5 Metoda nástup(IPosuvný) Nástup pasažéra bude velmi jednoduchý. Naprogramujeme jej tak, že požádáme přesouvač, aby pasažéra přesunul na pozici výtahu. Musíme však zařídit, aby pasažér při nastupování vyvolal dojem, že vchází do výtahu a vykresloval se proto před tím, než se vykreslí výtah. Dosáhneme toho tak, že před tím, než pasažéra předáme přesouvači, aby s ním „nastoupil“, požádáme aktivní plátno zavoláním metody přidejPod(IKreslený,IKreslený), aby pasažéra umístilo pod výtah. Této metodě však nemůžeme předat pasažéra jako parametr, protože není typu IKreslený. My sice víme, že je, ale překladač to neví – pro něj objekty typu IPosuvný nejsou typu IKreslený. Budeme si proto muset pomoci operátorem přetypování (viz kapitola Operátor přetypován (typ) na straně 188), takže nazveme-li parametr metody pasažér, bude mít volání metody tvar: AP.přidejPod( výtah, (IKreslený)pasažér ); Definujte tělo metody nástup() a spuštěním testu NástupVýstup ověřte, že pasažéři do výtahu skutečně nastoupí. Test sice skončí s chybou, protože pasažéři nevystoupí, ale tu napravíme hned v následujícím kroku. Metody výstupVpravo() a výstupVlevo() Když už jsme naučili pasažéra nastoupit, tak bychom jej měli také naučit vystoupit. Víme, že pro výstup z výtahu jsme si naplánovali metody dvě: výstupVpravo() a výstupVlevo(). Podívejme se tedy na ně. Začneme např. s výstupem vpravo. Na první pohled se zdá nejjednodušší prostě nechat pasažéra přesunout o šířku políčka doprava. Problém je, že nemáme pasažéra. Budeme muset definovat potřebný atribut (nazvěme jej třeba pasažér) a pak upravit metodu nástup(IPosuvný), aby do tohoto atributu uložila odkaz na nastoupivšího pasažéra. Upravte tedy metodu nástup(IPosuvný) a znovu pusťte test NástupVýstup, abyste se přesvědčili, že jste nic nepokazili. Pak definujte tělo metody výstupVlevo() a znovu pusťte test. Teď už by měl výstup vlevo chodit. Doplňte symetricky metodu výstupVpravo() a znovu spusťte prověřovací test. Předpokládám, že stále bude vše v pořádku. Test převozu pasažéra Vyzkoušejte test PřevozPasažéra. Pokud jste vše naprogramovali tak, jak jsem to na vás narafičil, měl by se před vámi nyní objevit problém: pasažér sice do výtahu nastoupí, ale výtah odjede bez pasažéra. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 286 z 433 Kapitola 6: Rozhraní 287 Jedním z možných řešení je pasažéra po nástupu odhlásit ze seznamu objektů obhospodařovaných aktivním plátnem, takže se pak přestane na plátno kreslit plátno to po něm přestane chtít). Před výstupem jej budeme muset opět přihlásit, ale to už naše metoda dělá. Upravte tedy metodu nástup(IPosuvný) a zkuste znovu test PřevozPasažéra. Pokud jste opět udělali to, co jsem vám poradil, měl by nyní výtah sice v pořádku odjet, ale při vystupování by se měl pasažér objevit na poli, z nějž před chvílí výtah odjel, a odtud se přesouvat do cílové výstupní pozice. Už asi víte, kde je chyba – pasažéra jsme měli před přihlášením k plátnu nejprve zavoláním setPozice(int,int) přemístit na políčko, na němž se zrovna nachází výtah. Opravte proto znovu metodu výstupVlevo() a prověřte opravu testem. Nyní bychom měli provést totéž i s metodou výstupVpravo(), ale to by se nám už mohlo začít zdát, že se příliš velká část kódu opakuje na několika místech. Mohli bychom to napravit např. tak, že bychom definovali soukromou pomocnou metodu výstup(int), které bychom předali jako parametr počet bodů, a o něž se bude pasažér přesouvat – metoda výstupVlevo() by zadávala zápornou hodnotu a metoda výstupVpravo() hodnotu kladnou. Definujte novou metodu, přesuňte do ní kód z těla metody výstupVlevo() a upravte ji tak, aby velikost vodorovného přesunu přebírala jako parametr. Pak upravte metody výstupVlevo() a výstupVpravo() tak, aby využily jejích služeb. Upravený program opět otestujte. Metody odvezVpravo(IPosuvný,int) a odvezVlevo(IPosuvný,int) Zbývají nám poslední dvě metody a poslední předpřipravený test. Definice těchto souhrnných metod by ale měla být pro vás již hračkou: zavoláte pasažérovi výtah, necháte jej nastoupit, odvezete jej do požadovaného patra a tam jej necháte vystoupit zadaným směrem. Opět ale máme dvě metody, který mají většinu kódu stejnou, takže bychom opět mohli definovat nejprve pomocnou soukromou metodu, která realizuje potřebnou činnost až po odvoz do zadaného patra a původní metody již pouze přidají výstup požadovaným směrem. Naprogramujte nyní vše potřebné a celý program vyzkoušejte testem Odvez. Pokud jste někde nezazmatkovali, měl by test proběhnout bez problémů. Jedním z možných řešení je definovat pro pasažéra speciální atribut a po jeho nástupu do tohoto atributu umístit odkaz na nastoupivšího pasažéra. Protože pasažér je typu IPosuvný, bude muset být i tento atribut typu IPosuvný. Tím se ale problému nezbavíme, protože pasažéra budeme muset také nakreslit. Jinými slovy: budeme potřebovat, aby metoda nakresli mohla nakreslit @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 287 z 433 288 Myslíme objektově v jazyku Java 1.5 nejprve obdélník výtahu a na něm převáženého pasažéra. Jenomže na aktivní plátno můžeme kreslit pouze pomocí kreslítka, ale objekty typu IPosuvný nám nic takového nezaručí. My naštěstí víme, že náš posuvný objekt bude určitě implementovat také rozhraní IKreslený. Metoda nakresli() pak bude mít na starosti nakreslení výtahu následované nakreslením pasažéra. Tím ale hned vyvoláme další problém: překladač odmítne pasažéra nakreslit, protože není typu IKreslený (my víme, že je, ale on to neví). V projektu 06_Rozhraní_Z najdete ještě dvě další verze výtahových tříd. Třída VýtahD nepředvádí nástup pasažéra z boku, ale zepředu. Pasažér se v ní viditelně přesune na pozici výtahu, který za ním potom zavře dveře. Třída VýtahP pracuje podobně jako předchozí třída, ale tento výtah nemá dveře, takže pasažér je po celou dobu přesunu vidět. Spusťte test Odvez ve třídách VýtahDTest a VýtahPTest, prohlédněte si chování těchto modifikovaných výtahů a pokuste se definovat odpovídající modifikované verze sami. 6.11 Shrnutí – co jsme se naučili Seznámili jsme se s novou sadou grafických tříd, jejímž jádrem je třída AktivníPlátno, která pracuje jako manažer a je schopna zabezpečit korektní vykreslení všech jí svěřených grafických tvarů. Vedle klasických tříd, které definují metody, jež opravdu něco dělají, zavádí Java ještě speciální třídy označované jako rozhraní, které pouze deklarují požadované vlastnosti a neobsahují žádnou implementaci. V těle rozhraní jsou proto uvedeny pouze hlavičky deklarovaných metod ukončené středníkem. Rozhraní definujeme obdobně jako třídu, pouze v hlavičce uvedeme místo klíčového slova class klíčové slovo interface. Všechny metody deklarované v rozhraní jsou veřejné, a to i v případě, kdy u nich není explicitně uveden modifikátor public. Rozhraní může být implementováno standardní třídou (class). Třída může implementovat libovolný počet rozhraní. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 288 z 433 Kapitola 6: Rozhraní 289 Implementaci rozhraní definujeme v prostředí BlueJ natažením implementační šipky od implementující třídy k implementovanému rozhraní. Třída, která implementuje rozhraní, se musí k této implementaci přihlásit tím, že v hlavičce za svým názvem uvede klíčové slovo implements následované seznamem názvů implementovaných rozhraní. Třída, která implementuje nějaké rozhraní, musí implementovat (tj. definovat) všechny metody deklarované v těle rozhraní. Instance třídy implementující nějaká rozhraní se může vydávat za instanci kteréhokoliv z implementovaných rozhraní. Potřebujeme-li, aby program považoval instanci nějakého typu za instanci jiného typu, můžeme ji přetypovat. Operátor přetypování pak za běhu zkontroluje, je-li požadované přetypování korektní. Potřebujeme-li definovat vlastnosti nějaké skupiny objektů, např. parametrů metody, můžeme definovat rozhraní, v němž požadované vlastnosti deklarujeme. Všechny třídy, u jejichž objektů budeme chtít mít zaručené dané vlastnosti, pak budou toto rozhraní implementovat. Návrhový vzor Služebník (Servant) použijeme tehdy, budeme-li potřebovat definovat nějakou službu, která bude pracovat s instancemi různých typů. Definujeme proto rozhraní, v němž specifikujeme požadované vlastnosti těchto objektů, a příslušné služební metody budou mít mezi svými parametry uveden objekt daného typu. Refaktorování je postup, při němž v drobných krocích upravujeme náš program tak, aby se zlepšila jeho architektura a tím se usnadnila jeho případná další vylepšení. Základem refaktorování je zásada, že úpravy musejí probíhat opravdu v malých krocích a po každém kroku se znovu spustí testy ověřující, že jsme tímto krokem nenarušili funkčnost programu. Návrhový vzor Stav použijeme tehdy, budou-li se reakce objektů dané třídy výrazně lišit v závislosti na stavu, v němž se objekty právě nacházejí. Při jeho realizaci definujeme nové, stavové rozhraní popisující stavově závislou část objektu. V definici multistavové třídy deklarujeme atribut zastupující stavově závislou část jejích objektů jako odkaz na toto stavové rozhraní. Zároveň definujeme skupinu stavových tříd implementujících ono stavové rozhraní, přičemž každá z těchto tříd bude specifikovat chování objektu v jednom z @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 289 z 433 290 Myslíme objektově v jazyku Java 1.5 možných stavů. Přepínání mezi stavy realizujeme voláním metod vracejících instanci jiné stavové třídy. Nové termíny @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 290 z 433 Kapitola 7: Co takhle něco zdědit? 7. 291 Co takhle něco zdědit? Kapitola 7 Co takhle něco zdědit? ☯ Co se v kapitole naučíme V této kapitole se seznámíme s možností, aby jedno rozhraní dědilo od jiného rozhraní. Vysvětlíme si, jaké jsou mezi rozhraními rodičovské vztahy a co to znamená, když je jedno rozhraní potomkem druhého. Ukážeme si také, že rozhraní může mít i několik rodičů a co z toho plyne. V druhé části kapitoly se seznámíme s návrhovým vzorem Zástupce a na závěr zkusíte dotvořit aplikaci, která tento návrhový vzor používá a která simuluje provoz kabinek na uzavřené lince tvořené řadou zastávek. V této kapitole nebudeme otevírat nový projekt. Místo toho budeme pokračovat ve vylepšování projektu z minulé kapitoly. Konečnou podobu celého projektu jakož i některé mezistupně najdete v projektu 07_Dědění_rozhraní_Z. V poznámce šťouralům na str. 270 jsem říkal, že přesouvač u svých instancí skrytě předpokládá, že implementují rozhraní IKreslený (přesouvat plynule objekt, který není vidět, přeci nemá smysl). Definicí takového chování jsem ovšem naprogramoval něco, co může vést při běhu programu k havárii, protože se klidně může stát, že někdo definuje třídu, která bude implementovat rozhraní IPosuvný, ale nebude implementovat rozhraní IKreslený. Přitom to vůbec nemusí být autovým úmyslem – stačí, když se jen zapomene o implementaci rozhraní zmínit v hlavičce. Jestli si ještě vzpomínáte na dělení chyb do tří skupin (viz kapitola Ladění na straně 95), tak si možná vybavíte nepříjemnou vlastnost takových chyb: i když nás program na chybu upozorní, neumí nám říct, kde jsme ji udělali. Řekne nám pou@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 291 z 433 292 Myslíme objektově v jazyku Java 1.5 ze, kde na ni přišel. A to může být úplně jinde, než tam, kde chyba vnikla a kde je ji třeba opravit. (Uvedený příklad to ostatně názorně dokazuje.) Potřebovali bychom, aby bylo možno překladači sdělit, že má hned při volání metody zkontrolovat, že instance implementuje obě rozhraní. V této kapitole se budeme zabývat jednou z možností, jak toho dosáhnout – využijeme toho, že rozhraní mohou od sebe dědit. 7.1 Co to je, když rozhraní dědí? Dědění rozhraní je věc naprosto jednoduchá: když při konstrukci jednoho rozhraní (říkejme mu dceřiné) dospějeme k závěru, že by třídy implementující toto rozhraní měly současně implementovat i jiné rozhraní (budu mu říkat rodičovské), oznámíme prostě v hlavičce dceřiného rozhraní, že toto rozhraní rozšiřuje (extends) rodičovské rozhraní a tím pro nás práce končí. Tím, že oznámíme, že dceřiné rozhraní rozšiřuje rodičovské rozhraní, zařídíme, že do portfolia metod dceřiného rozhraní přibudou k deklarovaným metodám i metody rodičovského rozhraní. O těchto metodách říkáme, že je dceřiné rozhraní od svého rodiče zdědí. (Dceřiné rozhraní je přitom může, ale také nemusí uvést ve své definici.) Kdokoliv chce dceřiné rozhraní implementovat, musí vedle jeho „vlastních“ metod implementovat i metody zděděné. Instance tříd, které implementují nějaké dceřiné rozhraní, se proto mohou vydávat i za instance jeho rodičovského (v případě delší dědické posloupnosti i prarodičovského, praprarodičovského apod.) rozhraní, protože z principu dědičnosti musí mít jeho metody implementovány. Kdykoliv se pak nějaká třída přihlásí k implementaci dceřiného rozhraní, překladač zkontroluje, že implementuje jak metody deklarované v dceřiném rozhraní, tak metody jejího rodičovského rozhraní. Náhodný překlep, o kterém jsem hovořil na počátku kapitoly, se nám tak podaří přesunout z běhových chyb mezi chyby syntaktické, tj. mezi ty, které se nejsnadněji hledají a opravují. Podívejme se na náš příklad s geometrickými tvary. Jak jsem říkal před chvílí, přesouvat plynule objekt, který není vidět, nemá smysl. (Neviditelné obrazce se mohou klidně přesunout skokem, protože to stejně nikdo nepozná.) Rozhraní IPouvný proto definujeme jako potomka (mohli bychom také říci rozšíření) rozhraní IKreslený. Předhodíme-li nyní přesouvači objekt typu IPosuvný, budou všichni vědět, že by případné umístění tohoto objektu na plátno mělo proběhnout bez problémů. Přesouvač již nebude muset daný objekt při umísťování na plátno přetypovávat, protože instance typu IPosuvný jsou zároveň instancemi typu IKreslený. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 292 z 433 Kapitola 7: Co takhle něco zdědit? 7.2 293 Jak to zařídit Teď už nám zbývá se pouze naučit dědičnost rozhraní realizovat. V prostředí BlueJ je postup jednoduchý: natáhnete dědickou šipku (použijete stejné tlačítko, jako při natahování implementační šipky, tj. šipku s trojúhelníkovým hrotem) od dceřiného rozhraní k rodičovskému rozhraní. Vyzkoušejte si to a natáhněte šipku do rozhraní IPosuvný k rozhraní IKreslený. Ve zdrojovém kódu definujeme dědění od nějakého rodičovského rozhraní tak, že do hlavičky dceřiného rozhraní přidáme za název rozhraní klíčové slovo extends následované názvem děděného rozhraní. V případě rozhraní IPosuvný tedy BlueJ upraví po natažení dědické šipky jeho hlavičku do tvaru: interface IPouvný extends IKreslený Obrázek 7.1 Po doplnění dědičnosti rozhraní je náš diagram třídy zbytečně „přešipkovaný“ @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 293 z 433 294 Myslíme objektově v jazyku Java 1.5 Po této úpravě samozřejmě zneplatní nejenom kód nově nastaveného dceřiného rozhraní, ale i kód všech tříd, které je implementovaly nebo používaly ve svých metodách parametry daného typu, ale to vás asi nepřekvapilo. Stačí stisknout tlačítko Přeložit a vše bude za chvilku opět připraveno k použití. Jak jistě odhadujete, je možný i obrácený postup, tj. zanesení informace o dědičnosti přímo do zdrojového kódu dceřiného rozhraní, na které BlueJ po překladu (nebo uložení) zareaguje natažením příslušné dědické šipky od tohoto rozhraní k deklarovanému rodičovskému rozhraní. Vyzkoušejte si to na rozhraní INafukovací. Duplicitně deklarovaná implementace Tak máme v našem projektu (obr. 7.1) opět poněkud „přešipkováno“ a je nejvyšší čas to napravit. Na začátku této kapitoly jsme si říkali, že každá třída, která implementuje nějaké rozhraní, automaticky implementuje i všechny jeho rodiče. A toho teď využijeme. Vezmete-li implementační a dědické šipky jako ukazatel toho, za čí instance se mohou instance třídy, z níž vede šipka, vydávat, zjistíte, že k rozhraní IKreslený vedou od „šipkujících tříd“ dvě nebo tři šipkové cesty: jedna přímá, druhá vedoucí přes rozhraní IPosuvný a u některých tříd ještě třetí vedoucí přes rozhraní INafukovací. Nic se tedy nestane, když „přímou šipku“ smažeme. U všech tříd našeho projektu, které implementují rozhraní IPosuvný nebo INafukovací tedy můžeme klidně smazat implementační šipku vedoucí k rozhraní IKreslený. Tím se nám obrázek přeci jenom trochu zpřehlední (bohužel ne nadlouho) – viz obr. 7.2. 7.3 Společný potomek několika rozhraní Když jsme spolu probírali návrhové vzory, tak jsem vám na konci povídání o jedináčkovi zadal úkol naprogramovat třídu ČernáDíra (najdete jej na str. 235), která přitáhne a spolkne zadaný obrazec. Tenkrát jsme ono přitahování a polykání museli realizovat trhaně. Nyní, když mám k dispozici přesouvače a kompresory můžeme snadno dosáhnout toho, že bude celý proces probíhat elegantně a plynule. Pokusme se nyní vymyslet, jak definovat třídu ČernáDíra2, aby byl celý proces nejenom plynulý, ale abychom také nemuseli pro každý typ polykaných objektů definovat vlastní metodu. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 294 z 433 Kapitola 7: Co takhle něco zdědit? 295 Černá díra objekty napřed přitáhne a potom spolkne. Aby je mohla plynule přitáhnout za pomoci přesouvače, potřebuje, aby objekty implementovaly rozhraní IPosuvný, aby je následně mohla spolknout za pomoci kompresoru, potřebuje aby implementovaly rozhraní INafukovací. Obrázek 7.2 Vzhled diagramu tříd po odstranění nadbytečných šipek Jak jistě tušíte (ostatně nadpis podkapitoly vám to napověděl), pomůžeme si tak, že definujeme rozhraní, které bude potomkem obou dvou. Nazveme je např. IHýbací. Jeho definice bude superjednoduchá (ani ji nebudu umísťovat do rámečku: interface IHýbací extends IPosuvný, INafukovací {} Všimněte si, že toto rozhraní má prázdné tělo – nedeklaruje žádnou vlastní metodu. Spokojí se totiž s tím, co zdědilo. Jinými slovy: kdokoliv se rozhodne implementovat rozhraní IHýbací, musí implementovat metody deklarované v @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 295 z 433 296 Myslíme objektově v jazyku Java 1.5 rozhraních IPosuvný a INafukovací (a tím pádem i v rozhraní IKreslený). Nic víc se po něm nechce. Pokud vás někoho napadlo, že druhá černá díra je vlastně služebník (i když to u černé díry zní divně), máte pravdu. I tady jsme si nejprve vymysleli (zanalyzovali), co potřebujeme a pak jsme nadefinovali rozhraní, které tyto naše potřeby deklaruje. Nyní nám zbývá připravit testy a pak vše implementovat a prozkoušet. Přidejte nyní rozhraní IHýbací do projektu a natáhněte všechny dědické a implementační šipky. V druhém kole pak zopakujte proces s odstraňováním duplicitních implementačních šipek, jak jsme to dělali před chvílí. Když ještě trochu přeskupíte třídy v diagramu a přidáte třídu ČernáDíra_7 spolu s její testovací třídou, měl by výsledný diagram vypadat podobně jako na obr. 7.3. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 296 z 433 Kapitola 7: Co takhle něco zdědit? 297 Obrázek 7.3 Projekt připravený pro naprogramování třídy ČernáDíra2 Takže si zopakujeme, co vlastně od nové černé díry chceme: stejně jako v minulé verzi má být jedináček, nabízet metodu getČernáDíra(), která vrátí odkaz na tohoto jedináčka, což je černý kruh usazený v prostředku plátna, instance bude mít metodu spolkni(IHýbací), která objekt získaný jako parametr přesune nad černou díru a tam jej zmenší a nakonec odstraní z plátna (spolkne). Když jsem pro vás připravoval tuto úlohu, uvědomil jsem si, že původní kompresor uměl měnit rozměr svěřené instance pouze tak, že neměnil její pozici. Naše dosavadní instance, jejichž pozice byla pozicí levého horního rohu opsaného obdélníku se proto zvětšovaly vpravo dolů a zmenšovaly vlevo nahoru. Kompresor to doposud nemohl dělat jinak, protože při nějakém elegantnějším zmenšování by potřeboval také měnit pozici daného objektu a to mu instance @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 297 z 433 298 Myslíme objektově v jazyku Java 1.5 rozhraní INafukovcí nezaručovaly. Když ale nyní máte k dispozici i rozhraní IHýbací, tak jsem pro vás možnosti kompresoru rozšířil. Stáhněte si z projektu 07_Dědění_rozhraní_Z novou verzi třídy PKompresor (=posouvací kompresor), jejíž instance jsou bohatší o přetížené verze metod, která očekává, že nafukovaný, resp. vyfukovaný parametr bude typu IHýbací a umožňuje zadat, kterým směrem je kotvící bod, který se při změně rozměru nebude hýbat. Zadáte-li jako parametr směru null, bude se zmenšovat do středu. 7.4 Návrhový vzor Stav (State) Všechna rozhraní, která jsme doposud v našich programech používali, realizovala návrhový vzor Služebník. Sloužila k tomu, abychom definovali požadované vlastnosti objektů, kterým může některá třída (AktinvíPlátno, Přesouvač, Kompresor) nabízet svoje služby. Použití rozhraní je však daleko širší. Jednomu z nich se budeme věnovat v této podkapitole. Povíme si o návrhovém vzoru Stav, při kterém rozhraní specifikuje objekty, které mohou být v různých stavech. Reakce těchto objektů na zprávy pak závisí na tom, v jakém stavu se zrovna daný objekt nachází. Projekt Šipky Představte si např. jednoduchou šipku, po které bychom chtěli, aby uměla reagovat na tři zprávy, tj. aby měla implementované tři metody: nakresli(Kreslítko) nakreslí šipku dodaným kreslítkem do příslušného políčka plátna, vpřed()přesune šipku na sousední políčko ve směru, do kterého je natočena, vlevoVbok() otočí šipku o 90° vlevo. Začnete-li se zamýšlet nad tím, jak byste potřebné metody naprogramovali, zjistíte, že podoba programu se bude lišit v závislosti na tom, do kterého směru je šipka právě natočena. Klasicky orientovaní programátoři by problém řešili asi tak, že by si vždy zjistili, do kterého směru je šipka právě natočena, a podle toho vybrali příslušnou reakci. My však nejsme programátoři klasičtí, ale objektově orientova- @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 298 z 433 Kapitola 7: Co takhle něco zdědit? 299 ní, a jako takoví máme mnohem širší možnosti (navíc jsem vám záměrně ještě nevysvětlil, jak se na něco zeptat a podle odpovědi se rozhodnout). Kdybyste věděli, do kterého směru je šipka natočena, jistě by pro vás nebyl problém definovat metody nakresli(Kreslítko) ani vpřed(). Metoda vlevoVbok() by sice byla trochu pracnější, ale určitě byste ji vymysleli také. Problém je ale v tom, že by se šipka poté, co se otočí, měla začít chovat úplně jinak. Prohlásíme-li směr, co nějž je šipka natočena, za její stav, tak otočením šipky měníme její stav a tím i veškeré její chování. Má-li šipka jiné chování, je rozumné uvažovat o tom, že bude instancí jiné třídy – zvolme pro tyto třídy např. názvy typu XŠipka, v nichž bude první písmeno označovat světovou stranu, do které jsou jejich instance natočeny, tj. názvy VŠipka, SŠipka, ZŠipka a JŠipka. Tyto třídy budou vytvářet jednostavové instance specializované pro pohyb v jednom směru. Teď ještě musíme vymyslet, jak to zařídit, aby se tyto čtyři třídy tvářili navenek jako třída jediná. Jednou z možností je definovat rozhraní (budu je v dalším textu označovat jako stavové rozhraní), které budou všechny čtyři specializované jednostavové třídy implementovat, a své instance pak vydávat za instance tohoto rozhraní. Toto stavové rozhraní bychom mohli v našem případě nazvat třeba IŠipka. Naše řešení však stále není dokonalé, protože pořád ještě nemáme vymyšleno, jak to zařídit, aby se při otáčení nahrazovala jedna šipka druhou. Mohli bychom to vyřešit např. tak, že by naše specializované jednostavové třídy definovaly místo metody vlevoVbok() metodu doleva(), která by vracela odkaz na jednostavovou šipku specializovanou pro pohyb v novém směru. Pro univerzální, několikastavovou šipku bychom pak definovali samostatnou třídu Šipka, která by jako svůj atribut uchovávala odkaz na tu správnou specializovanou jednostavovou šipku a při žádosti o otočku by svůj atribut požádala, aby ji za sebe poskytl příslušného nástupce. Abychom mohli do tohoto atributu uložit kteroukoliv ze specializovaných šipek, musíme jej definovat jako odkaz na instanci typu IŠipka, protože to je typ, za jehož instanci se může kterákoliv ze specializovaných jednostavových šipek vydávat. Po zakomponování všech těchto úvah do programu by definice třídy Šipka mohla vypadat následovně: 1 2 3 4 5 6 7 public class Šipka implements IPosuvný { //== KONSTANTY TŘÍDY =========================================================== private static final AktivníPlátno AP = AktivníPlátno.getPlátno(); //== ATRIBUTY INSTANCÍ ========================================================= @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 299 z 433 300 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 Myslíme objektově v jazyku Java 1.5 IŠipka šipka; //== KONSTRUKTORY A METODY VRACEJÍCÍ INSTANCE VLASTNÍ TŘÍDY ==================== public Šipka(int x, int y, Barva barva) { šipka = new VŠipka( x, y, barva ); } //== PŘÍSTUPOVÉ METODY ATRIBUTŮ INSTANCÍ ======================================= public int getX() { return šipka.getX(); } public int getY() { return šipka.getY(); } public void setPozice(int x, int y) { šipka.setPozice( x, y ); } //== PŘEKRYTÉ METODY IMPLEMENTOVANÝCH ROZHRANÍ ================================= public void nakresli(Kreslítko g) { šipka.nakresli( g ); } //== NOVĚ ZAVEDENÉ METODY INSTANCÍ ============================================= public void vlevoVbok() { šipka = šipka.doleva(); AP.překresli(); }//public void vlevoVbok() public void vpřed() { šipka.vpřed(); } }//public class Šipka Stavové rozhraní IŠipka byste jistě dokázali nadefinovat sami. Pro jistotu ale jeho možnou definici uvedu také: 1 public interface IŠipka extends IPosuvný 2 { @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 300 z 433 Kapitola 7: Co takhle něco zdědit? 301 3 void vpřed(); 4 IŠipka doleva(); 5 }//public interface IŠipka Definice jednostavových tříd specializovaných pro pohyb v jednom směru budou prakticky shodné. Např. třída VŠipka, jejíž instance se pohybují na východ, by mohla vypadat následovně: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 public class VŠipka implements IŠipka { //== KONSTANTNÍ ATRIBUTY TŘÍDY ================================================= private static final AktivníPlátno AP = AktivníPlátno.getPlátno(); //== PROMĚNNÉ ATRIBUTY INSTANCÍ ================================================ private Trojúhelník špička; private Obdélník tělo; //############################################################################## //== KONSTRUKTORY A METODY VRACEJÍCÍ INSTANCE VLASTNÍ TŘÍDY ==================== ); public VŠipka( int x, int y, Barva barva ) { int krok = AP.getKrok(); int k2 = krok/2; špička = new Trojúhelník( x*krok + k2, y*krok, k2, krok, barva, Směr.V } tělo = new Obdélník( x*krok, y*krok + krok/4, k2, k2, barva ); //== PŘÍSTUPOVÉ METODY ATRIBUTŮ INSTANCÍ ======================================= public int getX() { return tělo.getX(); } public int getY() { return špička.getY(); } public void setPozice(int x, int y) { int krok = AP.getKrok(); špička.setPozice( x + krok/2, y ); tělo .setPozice( x, y + krok/4 ); } //== PŘEKRYTÉ METODY IMPLEMENTOVANÝCH ROZHRANÍ ================================= public void nakresli(Kreslítko g) { @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 301 z 433 302 Myslíme objektově v jazyku Java 1.5 47 špička.nakresli( g ); 48 tělo.nakresli( g ); 49 } 50 51 //== NOVĚ ZAVEDENÉ METODY INSTANCÍ ============================================= 52 53 public IŠipka doleva() 54 { 55 int krok = AP.getKrok(); 56 return new SŠipka( getX()/krok, getY()/krok, špička.getBarva() ); 57 } 58 59 public void vpřed() 60 { 61 setPozice( getX()+AP.getKrok(), getY() ); 62 } 63 64 }//public class VŠipka Odsuneme-li z diagramu tříd všechny ostatní třídy někam stranou, mohl by diagram tříd naší šestice vypadat např. tak, jako no obr. 7.4: Obrázek 7.4 Diagram tříd podílejících se přímo na definici šipek (je zapnuto zobrazování šipek závislostí i dědičnosti) Oproti tomu, s čím jsme se setkávali doposud, má tento diagram jednu zajímavou vlastnost: cyklickou závislost specializovaných jednostavových tříd. Jejich instan@Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 302 z 433 Kapitola 7: Co takhle něco zdědit? 303 ce si při otáčení vlevo postupně předávají odpovědnost za správné vykreslení a chování šipky. Proto závisí první třída na druhé (metoda doleva() vrací její instanci), druhá na třetí, třetí na čtvrté a to opět na té první. Shrnutí Třídy v diagramu ještě trochu přemístíme a odstraníme šipky zobrazující vzájemné závislosti specializovaných jednostavových tříd. Ponecháme pouze jedinou závislost vedoucí od vícestavové třídy Šipka k stavovému rozhraní IŠipka. (viz obr. 7.5). Získáme tak diagram který názorně zobrazuje architekturu návrhového vzoru stav. Obrázek 7.5 Diagram lépe demonstrující možnou realizaci vzoru Stav Z toho, co jsme se doposud dozvěděli a co jsme si vyzkoušeli, bychom mohli sepsat následující zásady použití návrhového vzoru Stav: 1. Návrhový vzor Stav použijeme tehdy, nacházejí-li se objekty nějaké třídy v různých stavech, které se liší svými reakcemi na zasílané zprávy. 2. V definici třídy popisující takovéto objekty a jejich chování (tzv. mnohastavová třída) deklarujeme speciální atribut, který bude zastupovat část objektu, v níž jsou soustředěny stavově závislé vlastnosti objektů. Tento atribut specifikujeme jako instanci nového (stavového) rozhraní, které definujeme v následujícím kroku. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 303 z 433 304 Myslíme objektově v jazyku Java 1.5 3. Definujeme stavové rozhraní, v němž deklarujeme metody odpovídající zprávám, na něž reagují instance multistavové třídy rozdílně v závislosti na svém stavu. Spolu s těmito metodami musíme někdy deklarovat i další metody, kterou jsou potřebné pro komunikaci mezi multistavovou třídou a jejím jednostavovým atributem. Mezi deklarovanými metodami mohou být i takové, které vracejí odkaz na instanci jiné jednostavové třídy a pomáhají tak realizovat změnu stavu. 4. Po každý stav definujeme speciální jednostavovou třídu implementující toto rozhraní. Metody specifikující změnu stavu budou vracet odkaz na instanci jiné stavové třídy. Projekt Robot Definice všech tříd realizujících společně implementaci šipky naleznete v projektu 06_Rozhraní_Z. Stáhněte si je odtud a prozkoumejte jejich chování. Definujte třídu Robot mající podobné vlastnosti jako výše definovaná třída Šipka. Tato třída bude definovat půdorysný obrázek robota, který bude rozumět povelům krok() a vlevoVbok(). Obrázek robota navrhněte tak, aby z něj bylo zřejmé, do kterého směru je robot natočen. Pro inspiraci vám nabízím několik nápadů mých žáků: 7.5 Návrhový vzor Zástupce (Proxy) Pro tuto podkapitolu si z projektu 07_Dědění_rozhraní_Z zkopírujte do svého projektu rozhraní IZastávka a IMultiposuvný a třídy Testkabina, Kabina, KabinaRUP, Linka a Multipřesouvač. Již několikrát jsme si říkali, že jednou z největších vymožeností objektově orientovaného programování je schopnost zapouzdření implementačních detailů před všemi, kdo je nepotřebují znát. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 304 z 433 Kapitola 7: Co takhle něco zdědit? 305 Někdy se ale dostáváme do situace, kdy potřebujeme, aby instance vytvářené třídy měla řadu schopností (tj. aby uměla reagovat na řadu zpráv), ale většina z těchto schopností by měla sloužit pouze pro interní potřebu komunikace s nejbližšími spolupracovníky a před budoucími „koncovými uživateli“ by je bylo lepší zatajit, protože je za prvé nebudou potřebovat a za druhé by jejich nevhodným použitím mohli narušit některé důležité invarianty (= vlastnosti, které by se neměly měnit). Ukažme si to na příkladu, který jsem si pro vás připravil na závěr kapitoly a který má simulovat trať se zastávkami, po které jezdí kabiny lanovky (nebo něčeho jiného). Abyste o tomto příkladu získali lepší představu, spusťte si ve třídě KabinaTest test KabinaRUP. Mělo by se zobrazit aktivní plátno o rozměrech 7×7 polí, na němž budou tři soustředné různobarevné linky, po kterých jezdí číslované kabinky – viz obr. 7.6. Obrázek 7.6 Tři soustředné linky s pohybujícími se číslovanými kabinami Na vytvoření linky se podílí dvě třídy: Linka a Zastávka. Linka je vlastně cyklická posloupnost zastávek (cyklická proto, že za poslední zastávkou plynule následuje první). Aby mohla linka zařadit zastávku do svého seznamu, musí se zastávka umět napojit na svého budoucího předchůdce a následníka. Tuto svou schopnost však musí před ostatními třídami utajit, aby jí nějaký „nekalý živel“ nemohl požádat o začlenění do jiné linky, protože by kvůli tomu přetrhla posloupnost zastávek na své současné lince. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 305 z 433 306 Myslíme objektově v jazyku Java 1.5 Samozřejmě, že bychom mohli definovat další metodu, která by korektně vyjmula zastávku z jedné linky a začlenila ji do druhé, jenomže to nechceme. Naopak, chceme takovéto operaci zabránit. Linka to zařídí rafinovaně: definuje zastávku uvnitř sebe tak, aby to nikdo neviděl. Pokud ji někdo požádá o odkaz na zastávku (např. proto, že potřebuje znát její souřadnice, aby k ní mohl přijet), tak mu odkaz sice předá, ale předá mu jej jako instanci rozhraní IZastávka, takže bude možno volat pouze ty metody, které jsou v rozhraní deklarovány. Ostatní metody nebudou dostupné, protože z hlediska překladače instance rozhraní IZastávka žádné jiné metody nezná a nemůžeme je tedy volat. Překladač totiž nezajímá, že daná instance má implementovány ještě další metody, než ty, které jsou deklarovány v rozhraní. Pro něj instance rozhraní umí pouze to, co rozhraní deklaruje. Rozhraní IZastávka nám umožní pouze několik operací: jeho instance nám bude ochotna prozradit na které lince se daná zastávka nachází, jaké má souřadnice a která zastávka jí předchází a která za ní následuje. O jakékoliv další manipulace se zastávkou (např. její vyjmutí z linky) budeme muset požádat její linku, která si ohlídá, aby byla zastávka po vyjmutí z linky nepoužitelná a nemohla být využita k narušení linky. Instance rozhraní IZastávka tak slouží jako odstiňující zástupce skutečné instance. Podle toho také dostal tento návrhový vzor svůj název. Existují i jiné způsoby realizace odstiňujícího zástupce a existují i jiné druhy zástupců. Nelekněte se proto, pokud se někdy setkáte s implementací tohoto návrhového vzoru, která bude vypadat trochu jinak a řešit jiný trochu problém. Základní idea zástupce je, že aplikace dostane místo plnohodnotného původního objektu k dispozici jeho zástupce, který předá všechny požadavky aplikace zastupovanému objektu a výsledky, které zastupovaný objekt vrátí, předá naopak aplikaci. 7.6 Projekt Kabina Na závěr kapitoly jsem si pro vás zase připravil jeden (doufejme, že zajímavý) projekt. Využijeme před chvílí rozebíranou simulaci provozu kabin na linkách a zkusíte si vytvořit vlastní třídu Kabina, jejímiž instancemi budou kabiny pohybující se po linkách. Než vás ale „vypustím do světa“, abyste vše naprogramovali, @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 306 z 433 Kapitola 7: Co takhle něco zdědit? 307 proběhneme spolu nejprve nejdůležitější vlastnosti tříd a instancí, které budete ve svém projektu používat. Předpřipravené třídy Multipřesouvač Instance této třídy jsou vylepšenými přesouvači. Předáte-li multipřesouvači objekt k přesunu, nezdrží se v něm program po celou dobu, než se objekt přesune, ale multipřesouvač vrátí řízení volajícímu programu hned a zatímco se program zabývá svými záležitostmi, multipřesouvač přesouvá svěřený objekt. Klidně mu můžete vzápětí předat další objekt a on bude přesouvat oba najednou. Předáte-li mu rychle za sebou k přesunu třeba dvacet objektů, bude se na plátně pohybovat všech dvacet objektů najednou. O přesun objektu, který musí implementovat rozhraní IPosuvný požádáte multipřesouvač voláním metody přesuň, která má čtyři přetížené verze: dvěma z nich předáváte celočíselné souřadnice cílové pozice a dalším dvěma pak předáváte cílovou pozici jako objekt třídy Pozice. Při zadávání přesunu si můžete vybrat, zadáte-li dobu, za kterou má přesouvaný objekt dosáhnout cílové pozice a nebo jakou rychlostí se má k cíli pohybovat. Dobu přesunu zadáváte jako reálné číslo udávající počet sekund, rychlost zadáváte jako celé číslo udávající počet pixelů, o něž se objekt za sekundu posune. Nesmíte zapomenout na to, že se multipřesouvač orientuje podle typu zadaných dat. Budete-li tedy doba přesunu celé číslo (např. 2 sekundy), musíte ji přetypovat na double, protože jinak ji bude multipřesouvač považovat za rychlost. Vedle metod které odstartují přesun zadaného objektu nabízí multipřesouvač i metodu zastav(IPosuvný), kterou multipřesouvač žádáte o předčasné ukončení přesunu zadaného objektu. Protože multipřesouvač ví, že program nemusí vždy umět zjistit, jestli je dotyčný objekt ještě přesouván, nerozčílí se, když mu zadáte objekt, s nímž nehýbá, ale prostě neudělá nic. Multipřesouvač má dva konstruktory: první s celočíselným parametrem zadávající periodu opakování mikropřesunů svěřených objektů (zadává se v milisekundách) a druhý bezparametrický, který tuto periodu nastavuje na 50 ms, což znamená, že se obraz objektů překreslí asi 20krát za sekundu. Máte-li pomalý počítač, můžete tuto periodu zvětšit. IMultiposuvný Rozhraní IMiltiposuvný je potomkem rozhraní IPosuvný a rozšiřuje množinu jím vyžadovaných metod o metodu přesunuto(Multipřesouvač). Jak jsem již řekl, mul@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 307 z 433 308 Myslíme objektově v jazyku Java 1.5 tipřesouvač je ochoten přemísťovat jakýkoliv IPosuvný objekt. Pokud ale zjistí, že přesouvaný objekt je instancí třídy implementující rozhraní IMultiposuvný, připraví pro něj zvláštní bonus: po skončení přesunu mu tuto skutečnost oznámí právě zavoláním metody přesunuto(Multipřesouvač), přičemž se při volání sám předá jako parametr. To je od multipřesouvače chytrý marketingový tah. Instance implementující rozhraní IMultiposuvný totiž budou určitě pěkní cestovatelé a lze proto předpokládat, že po přesunu do první cílové pozice zatouží po přesunu do další. A koho o to požádají? No přece multipřesouvač, který získali jako parametr. Nebudou se přece pídit po jiném, když jednoho již mají v dosahu a z minulého přesunu s ním mají jistě dobré zkušenosti. IZastávka Jak jsem se již zmiňoval ve výkladu rozhraní Zástupce, instanci typu IZastávka vrací linka při žádosti o odkaz na svoji první zastávku. Instance tohoto rozhraní vám umějí prozradit svoji polohu, linku, ve které jsou začlenění, a předat odkaz na zastávku, která jim předchází a která za nimi následuje. Linka Již jsme si říkali, že linka je cyklickou posloupností svých zastávek. Chceme-li ji vytvořit, musíme jí zadat její barvu a souřadnice nejméně dvou jejích budoucích zastávek. Různé linky musí mít různou barvu, abychom na plátně snadno poznali, kudy která linka vede v případě, že budou dvě stanice nad sebou, jako je tomu například u Metra. Linka nabízí dva konstruktory: jeden požaduje vedle barvy linky ještě čtyři celá čísla představující souřadnice prvých dvou zastávek linky. Druhý pak požaduje vedle barvy pouze jeden parametr, kterým je objekt obsahující informace o souřadnicích nejméně dvou (ale může jich být libovolně mnoho) prvních zastávek vytvářené linky. Druhý konstruktor nechte prozatím mně – vy s ním budete umět pracovat až zvládnete práci s poli, kterým bude věnována kapitola Pole na straně 403. K vytvořené lince můžete přidávat další stanice pomocí metody přidejZa(IZastávka,int,int), které v prvním parametru předáte odkaz na stanici, za kterou se má přidávaná stanice zařadit, a v dalších dvou parametrech jí pak @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 308 z 433 Kapitola 7: Co takhle něco zdědit? 309 předáte souřadnice přidávané stanice. Metoda pak vrátí odkaz na právě přidanou zastávku. Abychom mohli nějakou zastávku přidat, musíme ale získat odkaz na jakoukoliv zastávku, která již na lince je. K tomu slouží metoda getPrvní(), která vrátí odkaz na první zastávku dané linky. Jak jsme si řekli před chvílí, od každé zastávky pak můžeme získat odkaz na jejího předchůdce a následníka. Zastávku na lince můžeme samozřejmě i odstranit – k tomu souží metoda odstraň(IZastávka). Kromě toho můžeme odstranit celou linku zavoláním metody zruš(). Vedle vytváření a rušení zastávek nám linka umožňuje také zjišťovat a nastavovat doporučenou rychlost kabin na lince a doporučenou dobu, po kterou se má kabina zdržet ve stanici než vyrazí směrem k další stanici. Navíc třída definuje tři statické metody, které vytvoří předem připravené pětiúhelníkové linky, a nimiž můžete následně experimentovat. Úloha – Kabina Prostředí jsme tedy podrobně rozebrali a můžete si s ním pohrát. Můžete vytvářet linky a přidávat k nim zastávky a pak zase zastávky odebírat a můžete i zkoušet posílat několik posuvných předmětů současně multipřesouvačem. Až si vyhrajete a seznámíte se s podpůrnými třídami, pokuste se o řešení následujícího úkolu: Definujte třídu Kabina, jejíž instance budou simulovat kabiny pohybující se po výše popsaných linkách. Třída a její instance budou mít následující vlastnosti: Třída bude definovat konstruktor Kabina(Linka), kterému předáme linku, na jejíž počáteční zastávku konstruktor kabinu po jejím vytvoření usadí a vypustí ji po trati. Kabiny se budou po trati pohybovat doporučenou rychlostí a budou v zastávkách čekat doporučenou dobu. Instance bude definovat metodu skonči(), po jejímž zavolání bude kabina odebrána z trati a smazána z plátna. Odebranou kabinu již nebude možno vrátit na trať, tj. nebude nutno definovat metody, které by to umožňovaly. Nápověda: Aby se kabina mohla pohybovat po členité trati linky, měla by třída Kabina implementovat rozhraní IMultiposuvný (a tím i všechny tímto rozhraním vyžadované metody) a využívat plně možností nabízených třídou Multipřesouvač. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 309 z 433 310 Myslíme objektově v jazyku Java 1.5 Při prvních složitějších úkolech jsem vás vedl krok za krokem „za ruku“. V minulé kapitole jsem již pouze stručně popsal řešení. Nyní vás zkusím vypustit do světa, abyste na správné řešení přišli sami. Není tak složité, takže věřím, že to dokážete i bez nahlížení do vzorového řešení ve třídě KabinaRUP. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 310 z 433 Kapitola 7: Co takhle něco zdědit? @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 311 Strana 311 z 433 312 Myslíme objektově v jazyku Java 1.5 Všechny potřebné třídy včetně testovací třídou a prázdné třídy Kabina určené pro vaše řešení jste si již stáhli na počátku podkapitoly Návrhový vzor Stav (State) Všechna rozhraní, která jsme doposud v našich programech používali, realizovala návrhový vzor Služebník. Sloužila k tomu, abychom definovali požadované vlastnosti objektů, kterým může některá třída (AktinvíPlátno, Přesouvač, Kompresor) nabízet svoje služby. Použití rozhraní je však daleko širší. Jednomu z nich se budeme věnovat v této podkapitole. Povíme si o návrhovém vzoru Stav, při kterém rozhraní specifikuje objekty, které mohou být v různých stavech. Reakce těchto objektů na zprávy pak závisí na tom, v jakém stavu se zrovna daný objekt nachází. Projekt Šipky Představte si např. jednoduchou šipku, po které bychom chtěli, aby uměla reagovat na tři zprávy, tj. aby měla implementované tři metody: nakresli(Kreslítko) nakreslí šipku dodaným kreslítkem do příslušného políčka plátna, vpřed()přesune šipku na sousední políčko ve směru, do kterého je natočena, vlevoVbok() otočí šipku o 90° vlevo. Začnete-li se zamýšlet nad tím, jak byste potřebné metody naprogramovali, zjistíte, že podoba programu se bude lišit v závislosti na tom, do kterého směru je šipka právě natočena. Klasicky orientovaní programátoři by problém řešili asi tak, že by si vždy zjistili, do kterého směru je šipka právě natočena, a podle toho vybrali příslušnou reakci. My však nejsme programátoři klasičtí, ale objektově orientovaní, a jako takoví máme mnohem širší možnosti (navíc jsem vám záměrně ještě nevysvětlil, jak se na něco zeptat a podle odpovědi se rozhodnout). Kdybyste věděli, do kterého směru je šipka natočena, jistě by pro vás nebyl problém definovat metody nakresli(Kreslítko) ani vpřed(). Metoda vlevoVbok() by sice byla trochu pracnější, ale určitě byste ji vymysleli také. Problém je ale v tom, že by se šipka poté, co se otočí, měla začít chovat úplně jinak. Prohlásíme-li směr, co nějž je šipka natočena, za její stav, tak otočením šipky měníme její stav a tím i veškeré její chování. Má-li šipka jiné chování, je rozumné uvažovat o tom, že bude instancí jiné třídy uloženo: – zvolme pro tyto třídy např. názvy typu XŠipka, v @Java_PO.doc, verze 0.33.197, út 29.6.04 – 23:55 Strana 312 z 433 nichž bude první písmeno označovat světovou stranu, do které jsou jeji h i t t č tj á VŠi k SŠi k ZŠi k JŠi k T t Kapitola 7: Co takhle něco zdědit? 7.7 313 Shrnutí – co jsme se naučili Rozhraní může dědit od jiného rozhraní. Rozhraní, které dědí, označujeme za potomka nebo dceřiné rozhraní, rozhraní od kterého se dědí označujeme za rodičovské rozhraní. Dceřiné rozhraní přebírá do svého „portfolia“ všechny metody deklarované v jeho rodičovském rozhraní. Potřebujeme-li použít v metodě parametr, který bude implementovat několik rozhraní, můžeme vytvořit rozhraní, které bude společným potomkem všech těchto rozhraní. Třída implementující dceřiné rozhraní musí implementovat metody jeho i všech jeho rodičů. Deklaruje-li několik rodičovských rozhraní stejnou metodu, je považována za jednu a tu samou metodu a implementující třídy ji implementují pouze jednou. Návrhový vzor Zástupce (Proxy) použijeme v případě, kdy potřebujeme předat uživatelům nějaké instance, avšak chceme jim zpřístupnit pouze některé z jejích vlastnosti. Při použití tohoto vzoru nedáme uživatelům k dispozici třídu, jejíž instance budou používat, ale pouze jejího zástupce, což může být např. rozhraní, které daná třída implementuje. Toto ochranné rozhraní (ochranný zástupce) zabezpečuje, že se uživatelé nemohou dostat k metodám dané třídy, které nemohou být soukromé, ale přitom není žádoucí, aby k nim měli uživatelé přístup. Nově zavedené termíny Termín @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 313 z 433 314 8. Myslíme objektově v jazyku Java 1.5 Třídy mohou také dědit Kapitola 8 Třídy mohou také dědit ☯ Co se v kapitole naučíme V této kapitole si ukážeme, že dědit od sebe mohou nejenom rozhraní, ale i třídy. Povíme si, čím se dědění tříd liší od dědění rozhraní, probereme jeho výhody i jeho slabiny a vysvětlíme si, za jakých podmínek je vhodné se pro dědění tříd rozhodnout. Dědění tříd patří nejen k nejvíce využívaným, ale také k nejvíce zneužívaným rysům objektově orientovaného programování. Proto si budeme povídat nejenom o tom, jak dědičnost používat, ale také si ukážeme, kdy bychom neměli podlehnout jejím svodům a měli bychom místo ní použít jiné řešení. Tato kapitola bude asi pro mnohé z vás patřit k nejtěžším kapitolám učebnice (mně dala také zabrat ☺). Bude to i proto, že v ní nebudou praktické příklady, ale pouze takové, na nichž budu demonstrovat probíranou látku. (V příští kapitole vám to vynahradím.) Začneme vysvětlením základních principů dědění tříd po němž bude následovat jejich předvedení v jednoúčelovém projektu sloužícím pouze k této demonstraci. V další části nadefinujeme nejprve jednodušší a poté i složitější potomky, a poté si ukážeme, jak je možné pro skupinu tříd se společnými vlastnostmi definovat také společného rodiče. V závěrečných podkapitolách si vysvětlíme, jaká nebezpečí s sebou dědění tříd přináší a naučíme se základní pravidla, podle kterých se budeme rozhodovat, kdy dědění tříd použít. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 314 z 433 Kapitola 8: Třídy mohou také dědit 8.1 315 Podtřídy a nadtřídy Specializace Pracujeme-li někde se skupinou osob, zvířat, věcí či vlastností, většinou záhy zjistíme, že v ní existují nejrůznější podskupiny se speciálními vlastnostmi a takže tuto skupinu často nějak definujeme a pojmenujeme a potom v některých situacích pracujeme už pouze s touto podskupinou. , čímž ji vydělíme že by se nám hodilo definovat specielní třídu právě pro tuto podskupiny. Ve škole nás např. učili, že obdélník, který má všechny strany stejně dlouhé má některé zvláštní vlastnosti, a aby se nám s ním lépe pracovalo, dostal i svůj název – čtverec. Obdobně bychom mezi elipsami mohli podmnožinu obrazců označovaných jako kruh, vegetariáni vydělují ze všech jídel ta, která neobsahují maso, a pouze taková jídla jsou ochotni konzumovat, běžný řidič se při výběru automobilu omezuje na osobní automobily a podobných příkladů byste jistě našli celou řadu. Tím, že jsme z dané množiny prvků vydělili prvky se speciálními vlastnostmi, ale nepřestaly tyto prvky patřit do své původní množiny. Tím, že čtverec dostal vlastní jméno, ještě nepřestal patřit mezi obdélníky. Každý čtverec je současně i obdélníkem stejně tak, jako je každý kruh elipsou, i když trochu zvláštní. Vegetariánské jídlo zůstává jídlem a osobní automobil automobilem. Možná vám to teď připadá samozřejmé, ale příliš mnoho programátorů na to zapomíná – ještě se k tomu vrátím. Zobecnění Téměř stejně často se setkáváme i s obráceným postupem, při němž zkušenosti získané se skupinou zvláštních objektů zobecňujeme na rozsáhlejší skupinu obecnějších objektů nebo vytváříme novou skupinu, která slučuje několik do té doby samostatných skupin. Zobecnění známých pravidel na rozsáhlejší skupinu objektů jste několikrát zažili v hodinách matematiky. V první třídě jste začali pracovat s přirozenými čísly. Tuto množinu jste pak rozšířili na čísla celá, poté na čísla racionální (zlomky) a ještě později na čísla reálná (sem patří např. číslo π nebo √2) a někteří z vás se již potkali i se závěrečným rozšířením na čísla komplexní. Ze školy byste měli znát i zavedení nové skupiny, do které jste pak zařazovali věci, o nichž jste před tím uvažovali odděleně. Pro malé děti jsou např. čtverec, trojúhelník a kruh rozdílné útvary. V pozdějších letech se naučí, že to jsou vlastně @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 315 z 433 316 Myslíme objektově v jazyku Java 1.5 speciální případy obecného geometrického tvaru, který je možné nakreslit či vymodelovat a pro nějž je např. možné změřit a spočítat obvod či obsah. Realizace v OOP Má-li objektové programování modelovat reálný svět, musí nabízet prostředky, jak tyto vlastnosti světa a jeho objektů zachytit. Jedním z nástrojů, které slouží k simulaci výše pospaných vlastností, je dědičnost tříd. Má-li nějaká množina instancí specifické vlastnosti, které je možno využít a/nebo naopak nutno respektovat, můžeme pro ni definovat zvláštní podtřídu, která zdědí všechny vlastnosti původní třídy a přidá ty, které popisují speciální vlastnosti dané podmnožiny. Můžeme např. definovat podtřídu Čtverec, mezi jejíž instance budou patřit takové obdélníky, které budou mít za každé situace oba rozměry stejně veliké. Obdobně můžeme definovat třídu Kruh, do níž budou patřit elipsy s trvale stejnými oběma rozměry. Obdobné je to i při zobecňování. Máme-li několik tříd, jejichž instance vykazují nápadně podobné vlastnosti, můžeme jim definovat společnou nadtřídu, které bude na jednom místě definovat společné vlastnosti všech svých podtříd, a každá z jejích podtříd bude moci definovat pouze ty vlastnosti svých instancí, kterými se tyto instance liší od instancí ostatních podtříd společné nadtřídy. Zůstaneme-li u našich geometrických tvarů, můžeme jim definovat společnou nadtřídu PosuvnéTvary, která bude definovat obecné vlastnosti posuvného tvaru (tj. má nějakou pozici a musí ji umět prozradit a nastavit a bude umět reagovat na skupinu posunových zpráv). Jiným příkladem by mohly být naše výtahy. Snad si ještě vzpomenete, že všechny tři ukázkové výtahy uměly prakticky totéž a lišili se pouze tím, jestli se za pasažérem zavíraly dveře a byl-li pasažér cestou ve výtahu vidět. Mohly bychom proto definovat pro všechny tři třídy společnou nadtřídu, která by definovala základní vlastnosti a metody a jednotlivé třídy by pak pouze doplnily specifika nástupu pasažéra do výtahu a jeho zobrazení při převozu do zadaného patra. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 316 z 433 Kapitola 8: Třídy mohou také dědit 317 Terminologie související s dědičností tříd je bohatá, a každý autor dává přednost něčemu jinému. V této knize proto termíny trochu střídat, abyste si zvykli na všechny a zbytečně vás při čtení další literatury nepřekvapily. Podtřída bývá mnohými autory označována jako odvozená třída nebo jako dceřiná třída. Výjimečně se setkáte i s termínem rozšířená třída, ale to většinou jen jako otrocký překlad. Já budu většinou používat termín dceřiná třída, ale občas použiji také některý ze zbylých dvou. Nadtřída bývá označována také jako základní nebo bázová třída anebo jako rodičovská třída. Já budu v duchu dědičnosti většinou používat termín rodičovská třída, ale stejně jako u dceřiné třídy dávám důraz na to většinou. Neustále mějte na paměti, že jednou z klíčových vlastností instancí dceřiných tříd je to, že se mohou kdykoliv vydávat za instance rodičovské třídy. Protože jsem se setkal se záplavou porušení této zásady, raději vám ji ještě vypíchnu: Dceřiná třídy blíže specifikuje vlastnosti podmnožiny instancí své rodičovské třídy. Instance dceřiné třídy musí být proto kdykoliv považovatelná za instanci kterékoliv její rodičovské třídy. Přetypování na předka nesmí být v rozporu s logickou aplikace Znám několik rádobyobjektových programátorů (někteří z nich bohužel dokonce píší učebnice nebo učí na vysokých školách), kteří za základní vlastnost dceřiných tříd považují to, že dceřiná třída svým instancím něco přidá, takže v jejich programech je např. úsečka nebo obdélník potomkem bodu. Protože instanci jakékoliv třídy je možno kdykoliv přetypovat na předka, muselo by v těchto aplikacích být možno definovat obdélník, jehož vrcholy by byly tvořeny obdélníky či úsečku jejímiž vrcholy by byly úsečky. Prosím vás, nezopakujte jejich chyby. Univerzální (pra)rodič Na závěr tohoto obecného úvodu se vrátím ještě na počátek výkladu OOP, kde jsem tvrdil, že podle objektově orientované programování tvrdí, že všechno je objekt. Z toho logicky vyplývá, že všechno musí být instancí třídy objektů. A sku@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 317 z 433 318 Myslíme objektově v jazyku Java 1.5 tečně. Java definuje třídu Object, která je rodičem (prarodičem, praprarodičem…) všech ostatních tříd. Třída Object je Adamem a Evou všech ostatních tříd. Z předchozího také logicky vyplývá, že všechny ostatní třídy jsou něčí dcery. Ty, které nesehnaly nějakého „lepšího“ rodiče jsou dcerami třídy Object. Mezi rodiče třídy se většinou počítají i její prarodiče. Zavedeme si proto termín vlastní nebo bezprostřední rodič třídy, který bude označovat jejího nejbližšího rodiče, tj. rodiče, který není jejím prarodičem. Budu-li v dalším textu hovořit o rodičích třídy, budu tím myslet jak její vlastní, bezprostřední rodiče, tak i případné prarodiče. Kdyby něco platilo pouze pro vlastní rodiče třídy a nevyplývalo to jasně z kontextu, výslovně to uvedu. Obdobně často říkáme, že objekt x je instancí třídy X i tehdy, je-li třída X rodičem třídy, kterou jsme o vytvoření objektu x požádali. Zavedeme proto termín vlastní třída instance, který budu používat ve chvíli, kdy budu potřebovat, že se jedná o třídu, která danou instanci „porodila“ a ne o některého z jejích rodičů. 8.2 ☯ Experimenty s dědičností V této podkapitole vám na speciálně navržených třídách předvedu, jak budou výše uvedené obecné zásady fungovat ve vašich programech. Postupně probereme všechny klíčové vlastnosti dědičnosti tříd, aby vám ve chvílích, kdy si nebudete některými aspekty vzájemného vztahu rodičovských a dceřiných tříd jisti, mohla tato podkapitola posloužit jako referenční příručka Obávám se, že toto je jedna z nejtěžších podkapitol celé knihy. Doporučuji vám proto k ní usednout odpočatí a svěží. Když vše hned napoprvé nepochopíte, nevadí, můžete se k ní vrátit později, až se s probíranými otázkami setkáte v dalších programech. Než začneme používat dědičnost k řešení praktických úloh v našich programech, zkusíme ji nejprve chvilku zkoumat na skupině jednoduchých tříd, které mají jediný účel: pomoci vám pochopit základní principy, mechanizmy a zákonitosti dědičnosti tříd. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 318 z 433 Kapitola 8: Třídy mohou také dědit 319 Pro naše experimenty s dědičností, jsem připravil samostatný projekt nazvaný 08_Dědičnost_tříd_pokusy (viz obr. 8.1), ve kterém jsou připraveny třídy, které vám mají pomoci si ujasnit, na co musíte být při používání dědičnosti ve svých programech připraveni a na co si máte dát pozor. Obrázek 8.1 Pomocný projekt pro výklad dědičnosti Univerzální rodič Object Jak jsem vám prozradil v minulé podkapitole, každá třída má svého rodiče. V Javě proto nemůžete definovat třídu, která by neměla žádného rodiče. Pokud jí žádného nepřidělíte, přidělí jí překladač univerzálního rodiče, kterým je třída Object. Já jsem tuto skutečnost před vámi doposud tajil, ale teď se vše provalilo. Přestože jsme žádné z našich tříd rodiče nepřiřazovali, všechny nějakého mají. Vynechám-li testovací třídy, jež mají speciálního rodiče, kterého jim přidělil BlueJ, pak všechny ostatní námi vytvořené třídy mají jako rodiče přiřazenu třídu Object. O tom se ostatně můžete hned přesvědčit. Vytvořte zcela prázdnou třídu (na obrázku jsem ji nazval Pokusná) a nechte zkonstruovat její instanci. Místní nabídka tohoto odkazu bude začínat příkazem zděděno z Object otevírajícím podnabídku se seznamem metod, které daná třída zdědila od třídy Object. Stejně začínají @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 319 z 433 320 Myslíme objektově v jazyku Java 1.5 místní nabídky libovolného odkazu na libovolnou instanci (chcete-li se přesvědčit, otevřete kterýkoliv z předchozích projektů a vyzkoušejte to). Většina zděděných metod je sice určena pro náročnější programátorské obraty, najdete však mezi nimi i metodu toString(), se kterou jsme již v našich programech pracovali a která vrací řetězcovou reprezentaci své instance. S děděním od třídy Object je to podobné jako s konstruktory: také platí, že každá třída musí mít konstruktor, takže pokud programátor žádný konstruktor nedefinuje, doplní za něj překladač implicitní. Obrázek 8.2 Seznam metod, které dědí každá třída od třídy Object Atributy a bezparametrické konstruktory tříd v projektu Podívejme se nyní na to, co jsem pro vás v projektu připravil. Třídy Matka, Dcera a Vnučka jsou prozatím bezprostředními potomky třídy Object. Jak ale jistě odhadnete, jsou určeny k tomu, abychom mezi nimi definovali rodičovské vztahy. Tyto tři třídy jsou doprovázeny dvěma testovacími třídami. První z nich, třída BezDědičnosti, je „duševně připravena“ na to, že mezi třídami Matka, Dcera a Vnučka nemusí být žádné dědické vztahy. Můžete ji proto hned přeložit a dokonce @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 320 z 433 Kapitola 8: Třídy mohou také dědit 321 se vám podaří úspěšně spustit první dva testy (další až do zavedení dědičnosti sice spustíte, ale neskončí úspěchem). Třídu Sdědičností budete moci přeložit až poté, co zavedete dědičnost, protože její metody s ní již počítají a překladač proto vyžaduje její správné nastavení. Třídy Matka, Dcera a Vnučka jsou si velice podobné. Každá obsahuje tři soukromé atributy, které jsou inicializovány již v deklaraci: atribut třídy x_počet (x zde zastupuje počáteční písmeno názvu třídy) obsahující počet vytvořených instancí, atribut instance x_pořadí s pořadovými číslem dané instance v rámci třídy a atribut x_název, který obsahuje název třídy následovaný pořadovým číslem příslušné instance. Kromě toho definuje každá z těchto tříd bezparametrický konstruktor tisknoucí na standardní výstup zprávu o vytvoření dané instance. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Matka { private static int m_počet = 0; private int m_pořadí = ++m_počet; private String název = "Matka_" + m_počet; public Matka() { System.out.println( "\nVytvářím " + m_pořadí + ". instanci třídy Matka " ); } //... Metody třídy a jejích instancí } Nejprve se podíváme, jak se třídy chovají před tím, než mezi nimi zavedeme příbuzenské vztahy. Nemáte-li ještě přeloženou třídu BezDědičnosti, přeložte ji a nechte naplnit zásobník odkazů z přípravku. V okně standardního výstupu se objeví text: Vytvářím 1. instanci třídy Matka Vytvářím 1. instanci třídy Dcera Vytvářím 1. instanci třídy Vnučka Spustíte-li test nebo požádáte-li o naplnění zásobníku odkazů z přípravku podruhé, bude vytvářet jejich druhé instance, poté třetí atd. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 321 z 433 322 Myslíme objektově v jazyku Java 1.5 Hierarchie dědičnosti Podívejme se nyní, co se stane, když zadáme očekávanou hierarchii dědičnost a jak se pak změní chování našich tříd. Dědičnost tříd zadáváme stejně jako dědičnost rozhraní, tj. natažením šipky s trojúhelníkovou hlavičkou od budoucí dceřiné třídy k jejímu budoucímu rodiči. Jediným viditelným rozdílem oproti rozhraním bude to, že natažená šipka nebude tentokrát čárkovaná ale plná – viz obr. 8.3. Správně by měla být použita plná šipka i pro dědičnost rozhraní, protože čárkovaná šipka označuje implementaci. Autoři se však rozhodli pro to, že všechny šipky vedoucí k rozhraní budou čárkované a my jejich rozhodnutí nemůžeme ovlivnit. Obrázek 8.3 Definice dědících tříd @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 322 z 433 Kapitola 8: Třídy mohou také dědit 323 Poté, co definujete dědičnost, bude malou chvíli jedno, kterou z testovacích tříd použijete. V obou jsou definovány stejné metody. Třídy se navzájem liší pouze tím, že třída BezDědičnosti byla upravena tak, aby jí byl překladač ochoten přeložit i tehdy, když ještě nebude zavedena dědičnost. Při pokusu o předčasný překlad třídy SDědičností, tj. o překlad před zavedením dědičnosti, by se překladač vzbouřil. I ve zdrojovém kódu se dědičnost tříd zapisuje stejně jako dědičnost rozhraní, tj. prostřednictvím klíčového slova extends následovaného názvem rodičovské třídy. Jedinou výjimkou je případ, kdy rodičovskou třídou je třída Object, jejíž rodičovství explicitně uvádět nemusíme. Nemusíme, ale můžeme. Přidáte-li do hlavičky kterékoliv třídy, která nemá uvedeného rodiče, klauzuli extends Object, překladač vám třídu bez problému přeloží. Zkuste např. přidat příslušnou klauzuli do hlavičky pokusné třídy a požádejte o její překlad. Překladač by měl uposlechnout bez námitek. Natáhněte nyní mezi třídami Matka, Dcera a Vnučka příslušné dědické šipky a podívejte se, jak se jejich hlavičky změnily. Nyní by měly mít tvar: public class Matka public class Dcera extends Matka public class Vnučka extends Dcera Záhlaví třídy Matka se nezměnilo, protože rodiče se v objektovém programování o své děti nestarají1. Veřejně označovat dědictví musí ve svém záhlaví pouze děti. To však znáte už od rozhraní, takže by vás to nemělo překvapit. Prozatím jsme dědictví tříd zadávali úplně stejně jako dědictví rozhraní. V jedné věci se však dědičnost tříd o dědičnosti rozhraní zásadně liší. Označíme-li rodiče deklarovaného v hlavičce třídy jako vlastního rodiče třídy, pak musí vždy platit: Třída musí mít vždy PRÁVĚ JEDNOHO vlastního rodiče! Jedinou třídou, která nemá rodiče, je třída Object. Ta je však zapuštěná hluboko do systému, takže může být trochu nestandardní. Pro všechny ostatní třídy výše uvedené pravidlo platí. BlueJ ani překladač vám nedovolí zadat pro třídu více než jednoho vlastního rodiče. Jakmile byste chtěli natáhnout od nějakého potomka druhou dědickou šipku, BlueJ by to nepochopil jako přidání rodiče, ale jako změnu rodiče – starý rodič by byl zapomenut a nastavil by se nový. 1 Jak jsme si říkali před chvílí, informaci o dědictví bychom sice mohli teoreticky přidat i do záhlaví třídy Matka – dědí přeci od třídy Object. V praxi se to však nedělá, takže i my necháme záhlaví v původním tvaru. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 323 z 433 324 Myslíme objektově v jazyku Java 1.5 Budete-li chtít BlueJ oblafnout a místo tahání šipek zapíšete ve zdrojovém kódu do hlavičky třídy dědění od několika rodičovských tříd tak, jak jsme to mohli udělat v případě rozhraní, odmítne překladač takovouto třídu přeložit. Nyní se nebudu odvolávat na naši analogii s továrnami na robotí vozidla, ale na souborový systém ve vašem počítači. S dědičností je to totiž stejné jako se složkami na disku. Každá podsložka může být pouze v jediné rodičovské složce, ale sama může mít libovolný počet svých podsložek. Obdobně je to i s rodiči: každá třída má právě jednoho rodiče, avšak může mít libovolný počet potomků. Podobjekt rodičovské třídy Každý objekt dceřiné třídy v sobě obsahuje podobjekt své rodičovské třídy se všemi jeho metodami a atributy. Instance dceřiných tříd tak dědí atributy svých rodičovských tříd, a to i tehdy, jsou-li tyto atributy soukromé a proto pro dceřinou třídu a její instance nepřístupné. Soukromé atributy podobjektu rodičovské třídy jsou však nepřístupné pouze po dceřiný objekt. Podobjekt rodičovské třídy je samozřejmě využívat může – jsou přece jeho, resp. jeho třídy. Pravidlo o podobjektu můžeme aplikovat rekurzivně. Má-li rodičovská třída svého rodiče (ten bude prarodičem její dceřiné třídy), bude tento podobjekt rodičovské třídy obsahovat svůj podobjekt své rodičovské třídy, která je prarodičem vlastní třídy původní instance. Tak to pokračuje dále až k pramáti všech tříd, kterou je třída Object (viz obr. 8.4). Obrázek 8.4 Podobjekty v objektech dceřiných tříd Každá instance má v sobě postupně zanořené podobjekty všech svých rodičů až k objektu třídy Object. Při vytváření objektu dceřiné třídy se vytvoří nejprve podobjekt rodičovské třídy a teprve pak se kolem něj začne budovat objekt dceřiné třídy. Rodičovský podobjekt se vytváří stejně jako všechny objekty, takže má-li jeho třída rodiče, vytvoří se nejprve podobjekt tohoto rodiče atd. atd. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 324 z 433 Kapitola 8: Třídy mohou také dědit 325 Výsledkem je to, že při vytváření každého objektu se nejprve vytvoří instance třídy Object, k ní se přidají rozšiřující atributy a metody a vytvoří se objekt potomka prvního řádu, k němu se přidají případné další atributy a metody a vznikne potomek druhého řádu a tak dále, až konečně vznikne požadovaná instance. Budu-li se potřebovat v dalším textu odvolat na konkrétní podobjekt instance, budu jej označovat jako Xxx-podobjekt, kde za Xxx dosadím název vlastní třídy příslušného podobjektu. Instance třídy vnučka by tedy měla podobjekty Dcera-podobjekt, Matka-podobjekt a Object-podobjekt. Pokud bychom se zase vrátili k naši analogii s vozidly, tak bychom mohli instance třídy Object prohlásit za tahače, za které se připojují přívěsy. V celém robotím městě je jediná továrna na tahače – tu představuje třída object. Všechna auta proto používají stejný typ tahače – instanci třídy Object. Každá ze zbylých továren pak k něm připojí svůj přívěs. Továrna, která odpovídá třídě, jež není přímým potomkem třídy Object, připojuje své přívěsy na konec soupravy, kterou dostává z továrny odpovídající příslušné rodičovské třídě. Aby však mohla připojit na konec svůj přívěs, musí nejprve obdržet od rodičovské třídy příslušnou soupravu – budoucí podobjekt své instance. Vše si předvedeme na našem pokusném projektu. Přeložte všechny třídy a požádejte některou z testovacích tříd, aby naplnila z přípravku zásobník odkazů. Po provedení operace by mělo okno terminálu vypadat jako na obr. 8.5. Obrázek 8.5 Okno standardního výstupu po vytvoření matky, dcery a vnučky @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 325 z 433 326 Myslíme objektově v jazyku Java 1.5 Výpis přesně demonstruje to, co jsme si říkali před chvílí. Testovací metoda nejprve vytvářela instanci třídy Matka – a konstruktor o tom poslal na standardní výstup zprávu. Druhým příkazem se vytváří instance třídy Dcera. Dcera je potomek matky, takže se musí nejprve vytvořit podobjekt matky (bude to již druhá matka v pořadí) a teprve po ní se dostane ke slovu konstruktor dcery, aby dokončil vytvoření příslušné instance. Třetí příkaz vytvářející vnučku nám vypíše dokonce tři řádky. Vnučka je potomkem dcery, musí se proto nejprve vytvořit dcera. Dcera je ale potomkem matky, takže nejprve nechá vytvořit matku (v pořadí již třetí) a až pak vytvoří dceru (druhou v pořadí). Teprve poté, co je dokončeno vytvoření podobjektu dcery, se dostane ke slovu konstruktor vnučky a dokončí vytvoření první vnučky. Správně bych měl v předchozích odstavcích hovořit ještě o tom, že třída Matka je potomkem třídy Object, takže se před vytvořením matky vytvoří nejprve podobjekt třídy Object. Jeho konstruktor však o svém působení žádnou zprávu nenechává, proto se o něm ze standardního výstupu nic nedozvíme. Můžeme se o něm pouze dohadovat na základě toho, jak se chovají ostatní potomci vůči svým rodičům. Pokud chcete celý proces pozorovat krůček za krůčkem, vložte zarážku na první příkazy všech konstruktorů a projděte si vytvoření instance třídy Vnučka krok za krokem. Podívejte se nyní do útrob jednotlivých instancí na jejich atributy. Zjistíte, že (podle očekávání): objekt matka má atribut m_pořadí a v něm je hodnota 1, objekt dcera má atributy d_pořadí s hodnotu 1 a atribut m_pořadí s hodnotou 2 – to je atribut jeho podobjektu rodičovské třídy. objekt vnučka má atributy v_pořadí s hodnotou 1, d_pořadí (atribut podobjektu) s hodnotu 2 a m_pořadí (atribut podpodobjektu) s hodnotou 3, třída Matka má atribut m_počet a v něm je hodnota 3, protože od této třídy jsou již vytvořeny tří instance – jedna jako samostatný objekt a dvě jako podobjekty instancí dceřiných tříd, třída Dcera má atributy d_počet s hodnotu 2 (jedna samostatná instance a jedna instance jako podobjekt vnučky) a zděděný atribut m_počet s hodnotou 3, @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 326 z 433 Kapitola 8: Třídy mohou také dědit 327 třída Vnučka má atributy v_počet s hodnotou 1 a zděděné atributy d_počet s hodnotu 2 a m_počet s hodnotou 3. To vše nám ukáže prohlížeč tříd. Neprozradí nám však to, které atributy jsou pro koho dostupné. Všechny atributy našich tříd jsou deklarovány jako soukromé. Z toho vyplývá, že s nimi mohou pracovat pouze konstruktory a metody definované ve stejné třídě. Pro ty ostatní jsou nepřístupné. Objekty tedy příslušné atributy zdědí, ale se žádným z nich nemohou nic dělat (tj. nemohou z něj číst ani do něj zapisovat), protože o něm oficiálně prostě nevědí. Pracovat mohou pouze s těmi rodičovskými atributy, které nejsou soukromé. Explicitní volání konstruktoru předka Již tedy víme, že každá instance dceřiné třídy (a to jsou vlastně všechny) v sobě obsahuje podobjekt své rodičovské třídy. Tento podobjekt je plnohodnotným objektem který musí být vytvořen rodičovským konstruktorem, jenž je volán dceřiným konstruktorem před tím, než dceřiný konstruktor začne se svojí prací na konstrukci dceřiného objektu. Jako obyčejně i tady dává Java programátorovi na výběr ze dvou možnosti: v automatické verzi zařídí volání konstruktoru rodičovského podobjektu překladač, v „ruční“ verzi je musí zabezpečit programátor. V automatické verzi vloží překladač před začátek dceřiného konstruktoru skryté volání bezparametrického konstruktoru rodičovské třídy, v ruční verzi si můžeme vybrat, který z rodičovských konstruktorů použít, ale jeho volání musíte explicitně zapsat. Aby mohl dceřiný konstruktor volat konstruktor svého podobjektu, musí na něj nejprve vidět. Rozhodne-li se rodičovská třída z nejrůznějších důvodů definovat bezparametrický konstruktor jako soukromý nebo jej vůbec nedefinovat, nezbude vám v dceřiné třídě než volat rodičovský konstruktor „ručně“. Zkuste si to. Otevřete zdrojový kód třídy Matka a opravte modifikátor bezparametrického konstruktoru na private. Požádáte-li nyní o přeložení třídy Dcera, překladač se vzbouří a oznámí vám: Matka(Java.lang.String) in Matka cannot be applied to () Zpráva nám říká, že se pokoušíme použít bezparametrický konstruktor (prázdné závorky na konci zprávy), zatímco ve třídě je k dispozici pouze jednoparametrický konstruktor jehož parametrem je textový řetězec. Chyby se můžete zbavit např. tak, že ve třídě Dcera trochu změníte definici bezparametrického konstruktoru. Zakomentujte jeho dosavadní tělo (vyberte blok @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 327 z 433 328 Myslíme objektově v jazyku Java 1.5 zasahující do obou řádků a stiskněte F8) a nechte jej, aby místo sebe vyvolal jednoparametrický konstruktor, kterému předá prázdný řetězec: 1 2 3 4 5 6 7 public Dcera() { this( "" ); // System.out.println( "Vytvářím " + d_pořadí + // ". instanci třídy Dcera " ); } Tím převedete veškerou zodpovědnost za správné zkonstruování instance (a za to, že bude volat dosažitelný rodičovský konstruktor) na druhý konstruktor, který je definován následovně: 1 public Dcera( String s ) 2 { 3 super( "- pro dceru " + s ); 4 System.out.println( "Vytvářím " + d_pořadí + 5 ". instanci třídy Dcera " + s ); 6 } Všimněte si prvního příkazu s klíčovým slovem super. Ten volá rodičovský konstruktor, kterému předává řetězcový parametr. Poté, co rodičovský konstruktor dokončí svoji činnost, vypíše na standardní výstup svoji zprávu. Pro volání rodičovského konstruktoru pomocí klíčového slova super platí naprosto stejná pravidla jako pro volání konstruktoru pomocí this. I toto volání musí být úplně prvním voláním v těle konstruktoru, před které smíte vložit pouze mezeru nebo komentář. Protože jak super tak this trvají na tom, aby byly prvními příkazy v těle konstruktoru, je jasné, že lze použít pouze jeden z nich. Při použití this nemusíte volat super, protože to za vás zařídí konstruktor, kterému pomocí this předáváte řízení. Badatelé určitě přijdou na to, že existuje způsob, jak zařídit, aby se před zavoláním rodičovského konstruktoru ještě provedla nějaká akce, ale já vám jej neprozradím, protože to není korektní postup. Necháte-li nyní naplnit zásobník odkazů, bude se pro dceru spouštět jednoparametrický konstruktor, který na konec řádku ještě připíše, že pracuje pro dceru. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 328 z 433 Kapitola 8: Třídy mohou také dědit 329 Restartujte nyní virtuální stroj (potřebujeme ve třídách vynulovat počitadla instancí) a spusťte test parametrických konstruktorů. V okně terminálu by se měl objevit následující text: Vytvářím 1. instanci třídy Matka Vytvářím 2. instanci třídy Matka - pro dceru Vytvářím 1. instanci třídy Dcera Vytvářím 3. instanci třídy Matka - pro dceru Vytvářím 2. instanci třídy Dcera Vytvářím 1. instanci třídy Vnučka Vytvářím 4. instanci třídy Matka MATKA Vytvářím 5. instanci třídy Matka - pro dceru DCERA Vytvářím 3. instanci třídy Dcera DCERA Vytvářím 6. instanci třídy Matka - pro dceru - pro vnučku VNUČKA Vytvářím 4. instanci třídy Dcera - pro vnučku VNUČKA Vytvářím 2. instanci třídy Vnučka VNUČKA V prvních třech sadách pracoval přípravek (stejný výstup jste viděli před chvílí), druhá sada je pak výsledkem toho, že testovací metoda vytvořila od každé třídy po jedné instanci za použití jednoparametrických konstruktorů přičemž konstruktoru vytvářené instance předala text obsahující název třídy zapsaný samými velkými písmeny (jestli vás zajímá, prohlédněte si zdrojový kód). Připadají-li vám výpisy podivné, opět vám doporučuji nastavit do všech konstruktorů zarážky, nechat vytvořit instanci vnučky a na počátku těla konstruktoru se pokaždé podívat, jak vypadá hodnota parametru. Chráněné atributy – modifikátor přístupu protected Často bychom potřebovali, aby potomci měli přístup k některým rodičovským atributům a metodám, ke kterým ale „zbytek světa“ pustit nechceme. Java pro tento účel definuje speciální modifikátor přístupu protected (chráněný), který označuje atributy a metody, k nimž budou mít kromě ostatních metod dané třídy přístup i metody jejích potomků. Vyzkoušejme jeho použití na naší třídě a změňme modifikátor přístupu k bezparametrickému konstruktoru třídy Matka na protected. Vraťte pak bezparametrický konstruktor třídy Dcera do původního stavu, tj. nastavte bezparametrický konstruktor matky jako veřejný, „odkomentujte“ zakomentované příkazy (vyberte je do bloku a stiskněte F7) a smažte první příkaz volající jednoparamet@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 329 z 433 330 Myslíme objektově v jazyku Java 1.5 rický konstruktor. Pak vše znovu přeložte. Jak se můžete přesvědčit, jde to bez problémů. Bohužel vám nemohu ukázat, jak se někdo jiný nemůže k chráněnému atributu probojovat, protože modifikátor protected zpřístupňuje daný atribut nejenom potomkům, ale také třídám v nejbližším okolí, což jsou v případě BlueJ všechny třídy ve stejném projektu. K tomuto tématu se proto vrátím v kapitole Budete si to přát zabalit? na straně 376, kde si ukážeme, jak je možné rozdělit celou úlohu do několika „projektů“ a kde se pak tento modifikátor uplatní. Dědičnost a metody tříd Konstrukci dceřiných tříd jsme tedy zvládli. Nyní nás čekají hrátky s metodami. Pochopit chování metod ve všech souvislostech patří k tomu nejtěžšímu, co vás v této učebnici potká. Pusťte se proto do jeho studia odpočatí s čerstvou hlavou. U rozhraní jsme to měli jednoduché: tam prostě metoda byla nebo nebyla. O detaily implementace jsme se nestarali, takže dědičnost spočívala pouze v rozšířen portfolia vyžadovaných metod. U tříd to bude složitější, protože tady se jedná nejenom o tom, jestli třída bude umět na danou zprávu zareagovat (tj. jestli má potřebnou metodu), ale také o tom, jak bude reagovat. U statických metod je to ještě poměrně prosté: metoda je vždy kvalifikována třídou, takže bude-li v několika „příbuzných“ třídách stejná metoda, bude v každém okamžiku jasné, která je volána. Na vyzkoušení jsem vám připravil test, jenž volá metodu zprávy(), která je v každé třídě definovaná. Její tělo jsem však zakomentoval protože po jeho odkomentování se diagram tříd zbytečně „zašipkuje“. Chcete-li si test vyzkoušet, odkomentujte těla všech tří metod, třídy přeložte a spusťte test. Jak ukazuje následující výpis, tato metoda volá nejprve statickou metodu zpráva(String) pro svoji instanci a poté volá tyto metody kvalifikované názvy jednotlivých tříd. Statická metody zpráva(String) je přitom definována pouze ve třídách Matka a Dcera. Vnučka musí použít verzi zděděnou od dcery. 1 public class Dcera extends Matka 2 { 3 public static void zpráva( String text ) 4 { 5 System.out.println( text + " (D)" ); 6 } @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 330 z 433 Kapitola 8: Třídy mohou také dědit 7 8 9 10 11 12 13 14 15 16 17 } 331 public void zprávy() { zpráva( "\nDcera - moje zpráva" ); Matka .zpráva( "- Zpráva matky" ); Dcera .zpráva( "- Zpráva dcery" ); Vnučka.zpráva( "- Zpráva vnučky" ); } //... Deklaraco atributů, konstruktorů a zbylých metod Test po svém vyvolání vypíše na standardní výstup následující zprávy: Matka - moje zpráva (M) - Zpráva matky (M) - Zpráva dcery (D) - Zpráva vnučky (D) Dcera - moje zpráva (D) - Zpráva matky (M) - Zpráva dcery (D) - Zpráva vnučky (D) Vnučka - Zpráva - Zpráva - Zpráva moje zpráva (D) matky (M) dcery (D) vnučky (D) Všimněte si, že vnučka, která vlastní verzi metody zpráva(String) definovanou nemá, si půjčuje potřebnou metodu od své rodičovské třídy (Metoda vypisuje za text do závorek počáteční písmeno názvu své třídy.) I když je rozpoznání požadované statické metody jednoduché, přesto se nedoporučuje definovat v příbuzných třídách stejně pojmenované statické metody, protože to, která se má zavolat, bývá občas jasné pouze překladači a virtuálnímu stroji, avšak jejich názor se může od názoru programátora lišit. Programátor totiž často podlehne svým falešným představám. Proto je lepší prostor pro chyby minimalizovat a pokud možno pojmenovávat metody různě. Metody instancí, jejich dědění a překrývaní Metody instancí dceřiných tříd můžeme rozdělit do tří skupin: metody, které třída nově definovala, @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 331 z 433 332 Myslíme objektově v jazyku Java 1.5 metody, které třída zdědila a používá je v té podobě, v jaké byly definovány v příslušné rodičovské třídě, metody, které třída sice zdědila, ale protože jí nevyhovovaly, definovala jejich vlastní verze. Na každou z nich se nyní na chvilku podíváme. Nové metody S metodami, které jsou v dané třídě nově definované byste neměli mít problémy, protože ty jste od počátku knihy definovali a doufám, že už jste se s nimi naučili pracovat. Nepřekryté zděděné metody Zděděné metody mají jednu zvláštnost: nejsou to vlastně metody dané instance, ale metody jejího podobjektu. Když tedy pošlete instanci zprávu, na níž musí reagovat některá ze zděděných metoda, instance zjistí, že pro danou zprávu nemá vlastní metodu a předá ji svému podobjektu, aby vše zařídil. Navenek se však tváři, že to všechno zařídila ona. To, že správnou reakci zprávu ve skutečnosti zprostředkoval její podobjekt, je stejně interní záležitost, jako to, jak to udělal. Obrátíme-li se opět k naší analogii, můžete si představit, že v továrně mají samozřejmě nejraději ten vagónek, který na konec vláčku přidali, a proto zařídí, že všechny telefony vedou právě sem. Tady se pak rozhoduje, jestli reakci na zaslanou zprávu zvládne některý z robotů v osádce posledního vozu (tj. metoda definovaná v dané třídě), nebo jestli budou muset požádat předchozí vagón, aby na zprávu zareagoval. Překryté zděděné metody Když třídě podoba některé zděděné metody nevyhovuje, může si definovat vlastní verzi této metody. Tato nová verze pak pro instance této třídy a jejích potomků překryje onu nevyhovující definici v rodičovské třídě. Kdykoliv proto instanci někdo pošle zprávu vyvolávající tuto metody, instance vždy použije svoji verzi metody a nijak se nepídí po tom, je-li v rodičovské třídě definována stejná (překrytá) metoda. Svoji verzi překryté metody použije instance i v případě, kdy je tato metoda volána z některé z metod rodičovské třídy. Je úplně jedno, že v době, kdy jsme definovali rodičovskou třídu, daná dceřiná třída ještě neexistovala. Jakmile v dceřiné @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 332 z 433 Kapitola 8: Třídy mohou také dědit 333 třídě některou z metod rodičovské třídy překryjeme, začnou ji instance dceřiných tříd používat všude, tj. i v metodách, které pouze zdědily. Pro jistotu bych jenom zopakoval: původní rodičovskou verzi metody budeme označovat jako překrytou a novou verzi definované v dceřiné třída budeme označovat jako překrývající. V naší analogii bychom si to mohli představit tak, že roboti (=metody), kteří mají reakci na zaslanou zprávu na starosti, vykonávají předepsanou posloupnost příkazů. Když je tam příkaz, že mají své instanci zaslat nějakou zprávu, nedrcnou do souseda, který ji má na starosti, ale opravdu své instanci pošlou danou zprávu a instance pověří jejím zpracováním příslušného robota. Když ale robot podobjektu pošle své instanci zprávu, pro kterou má připraveného vlastního robota-metodu, pověří instance reakcí vlastního (překrývajícího) robota, který zareaguje tak, jak má předepsáno. Překrytí se však neuplatní u soukromých metod třídy. Soukromou metodu nemůže dceřiná třída překrýt, protože o ní vůbec neví. Definuje-li proto dceřiná třída stejnou metodu, jako je některá ze soukromých metod rodičovské třídy, je tato metoda považována za zcela novou metodu, která se stejnojmennou soukromou metodou rodičovské třídy vůbec nesouvisí. V naší analogii bychom mohli říci, že soukromé metody jsou vlastně tajné, tak se nesmějí volat normálním způsobem. Narazí-li proto robot ve svém předpisu na příkaz, aby zaslal tajnou zprávu (=zprávu, na níž reaguje soukromá metoda), použije pro zaslání zprávy své instanci interní telefon. Proto zpráva nikdy neopustí vagón a nehrozí, že při připojení dalšího vagónu dorazí nejprve tam. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 333 z 433 334 Myslíme objektově v jazyku Java 1.5 Pravidla chování překrytých metod jsou natolik důležitá, že vám je pro jistotu zopakuji ještě jednou: Metody, které jsou pro potomka viditelné, může potomek překrýt vlastní definicí, které budou jeho instance vždy dávat přednost před původní zděděnou verzí. Nová, dceřiná verze metody se použije ve všech metodách dané instance, tedy i v nepřekrytých zděděných metodách, a to přesto, že v době definice těchto metod překrývající verze metody vůbec neexistovala. Soukromé metody nemohou potomci překrýt, protože o nich nevědí. Když tedy potomek definuje stejnou metodu (tj. stejně pojmenovanou a se stejnou sadou parametrů), jako je některá ze soukromých metod rodičovské třídy, je tato metoda považována za zcela novou metodou, která se svojí jmenovkyní nemá nic společného. Test chování překrývajících a překrytých metod Zase si vše vyzkoušíme v našem pokusném projektu. Otevřete si nejprve zdrojové kódy tříd a podívejte se na definice metod, které se jmenují stejně jako třída (samozřejmě s výjimkou velikosti počátečního písmene). Definice všech tří metod jsou téměř stejné – např. metoda matka() je definována následovně: 1 public void matka() 2 { zpráva( "\nMetoda matka() instance " + this ); 3 System.out.println( " Název podobjektu: " + název ); 4 soukromá(); 5 veřejná(); 6 System.out.println( název + ".matka - konec"); 7 8 } Metoda identifikuje instanci, které byla poslána příslušná zpráva, a přidá název podobjektu, jehož metoda se právě provádí. Pak zavolá soukromou metodu soukromá() a veřejnou metodu veřejná() a nakonec vypíše zprávu o svém ukončení. Jako identifikaci instance přitom použije text vrácený metodou toString(), o níž víme, že ji překladač automaticky zavolá ve chvíli, kdy narazí na odkaz na instanci při operaci slučování textových řetězců. Protože ani jedna z tříd nedefinuje @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 334 z 433 Kapitola 8: Třídy mohou také dědit 335 vlastní verzi této metody, bude použita verze zděděná od třídy Object. Připomínám, že tato verze vrátí řetězec, v němž je uveden název vlastní třídy instance následovaný znakem @ a hexadecimálním číslem, které lze považovat za adresu instance. Metody soukromá() a veřejná() jsou také téměř shodné, a to nejen mezi třídami, ale i navzájem. Vypíší na standardní výstup název třídy, v níž jsou definovány, a název sebe sama. Pak identifikují podobjekt, do jehož portfolia patří, a instanci, které byla zpráva předána. 1 private void soukromá() 2 { System.out.println(" Třída Matka, metoda soukromá(): " + 3 4 "\n Podobjekt: " + název + "\n Instance: " + this ); 5 6 } V tabulce 8.1 najdete výstup dvou textů. Test MatkaDceraVnučka zobrazený v levém sloupci volá pro každou instanci metodu pojmenovanou stejně jako její vlastní třída. Při těchto voláních se vliv překrytí neuplatní. Naproti tomu v pravém sloupci je výsledek testu MatkaMatkaMatka, který volá pro každou instanci metodu matka(). Volání metody pro instanci Matka je ještě totožné s testem vlevo, ale u volání této metody pro instance tříd Dcera a Vnučka se začne uplatňovat překrývání metod. Budete-li chtít vyzkoušet oba testy na svém počítači a porovnat výsledky, postupujte následovně: 1. Restartujte virtuální stroj (budou se vám pak lépe počítat instance) a spusťte test MatkaDceraVnučka, který pro každý objekt spustí metodu pojmenovanou stejně jako jeho třída. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 335 z 433 336 Myslíme objektově v jazyku Java 1.5 Tabulka 8.1: Porovnání výstupu překrytých a nepřekrytých metod 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 Test: MatkaDceraVnučka Test: MatkaMatkaMatka Vytvářím 1. instanci třídy Matka Vytvářím 1. instanci třídy Matka Vytvářím 2. instanci třídy Matka Vytvářím 1. instanci třídy Dcera Vytvářím 2. instanci třídy Matka Vytvářím 1. instanci třídy Dcera Vytvářím 3. instanci třídy Matka Vytvářím 2. instanci třídy Dcera Vytvářím 1. instanci třídy Vnučka Vytvářím 3. instanci třídy Matka Vytvářím 2. instanci třídy Dcera Vytvářím 1. instanci třídy Vnučka Instance matka (M) Instance matka (M) Metoda matka() instance Matka@ac6a45 Název podobjektu: Matka_1 Třída Matka, metoda soukromá(): Podobjekt: Matka_1 Instance: Matka@ac6a45 Třída Matka, metoda veřejná(): Podobjekt: Matka_1 Instance: Matka@ac6a45 Matka_1.matka - konec Metoda matka() instance Matka@ac6a45 Název podobjektu: Matka_1 Třída Matka, metoda soukromá(): Podobjekt: Matka_1 Instance: Matka@ac6a45 Třída Matka, metoda veřejná(): Podobjekt: Matka_1 Instance: Matka@ac6a45 Matka_1.matka - konec Instance dcera (D) Instance dcera (D) Metoda dcera() instance Dcera@175078b Název podobjektu: Dcera_1 Třída Dcera, metoda soukromá(): Podobjekt: Dcera_1 Instance: Dcera@175078b Třída Dcera, metoda veřejná(): Podobjekt: Dcera_1 Instance: Dcera@175078b Dcera_1.dcera - konec Metoda matka() instance Dcera@175078b Název podobjektu: Matka_2 Třída Matka, metoda soukromá(): Podobjekt: Matka_2 Instance: Dcera@175078b Třída Dcera, metoda veřejná(): Podobjekt: Dcera_1 Instance: Dcera@175078b Matka_2.matka - konec Instance vnučka (D) Instance vnučka (D) Metoda vnučka() instance Vnučka@42552c Název podobjektu: Vnučka_1 Třída Vnučka, metoda soukromá(): Podobjekt: Vnučka_1 Instance: Vnučka@42552c Třída Vnučka, metoda veřejná(): Podobjekt: Vnučka_1 Instance: Vnučka@42552c Vnučka_1.vnučka - konec Metoda matka() instance Vnučka@42552c Název podobjektu: Matka_3 Třída Matka, metoda soukromá(): Podobjekt: Matka_3 Instance: Vnučka@42552c Třída Vnučka, metoda veřejná(): Podobjekt: Vnučka_1 Instance: Vnučka@42552c Matka_3.matka – konec 2. Otevřete okno terminálu a příkazem Nastavení → Uložit do souboru uložte jeho obsah do textového souboru, který nazvěte podle testu MatkaDceraVnučka.txt. 3. Znovu restartujte virtuální stroj, spusťte test MatkaMatkaMatka, který pro každý objekt spustí metodu matka(). Obsah okna terminálu pak uložte do souboru nazvaného (překvapení!) MatkaMatkaMatka.txt. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 336 z 433 Kapitola 8: Třídy mohou také dědit 337 4. Otevřete oba soubory v editoru a umístěte jejich okna vedle sebe tak, abyste je mohli porovnávat jako já v tabulce. 5. K tomu si ještě otevřete některý ze zdrojových kódů, abyste mohli sledovat, kudy program v daném okamžiku prochází. Porovnání Pojďme se nyní podívat, v čem se oba výstupy shodují, v čem se liší a proč tomu tak je. Možná vás napadlo, že by bylo nejlepší prostě program odkrokovat a zjistit, jak funguje. To bych vám asi v tuto chvíli neradil, protože byste asi v programu brzy zabloudili. Obrňte se trpělivostí a pojďte s námi projít tabulku a související zdrojové kódy nejprve očima. Až vám začne být jasné, co má program v jednotlivých okamžicích dělat, můžete jej pak začít krokovat a utvrdit se v tom, že pracuje přesně podle předpokladů. Až do řádku 23 jsou oba výstupy totožné. Aby také ne, když v obou případech děláme totéž: vytvoříme přípravkem instance a zavoláme metodu matka() instance matka(). V řádku 24 najdeme prvou odchylku. Z textu řádku vyčteme, že v levém sloupečku voláme metodu dcera.dcera, kdežto v pravém dcera.matka(). To ale víme, zajímavé to proto začne být až od řádku následujícího. Podobjekt Zde se totiž dozvídáme, že ve skutečnosti voláme metodu podobjektu Matka_2. Došlo na to, o čem jsme si před chvílí povídali. Dceři byla poslána zpráva, pro kterou neměla připravenou vlastní metodu, a proto předala zprávu svému Matka-podobjektu, aby na ni zareagoval (jak si můžeme přečíst v 3. řádku, Matka-podobjektem první dcery je druhá matka). Ten, kdo poslal dceři zprávu, si ale bude myslet, že na ni zareagovala dcera, protože nemá šanci zjistit, jak to má dcera uvnitř instance zorganizované. Soukromá metoda Poté, co vypíše informace o tom, s kterými instancemi máme tu čest, zavolá metoda matka() soukromou metodu soukromá() a ta se nám ne řádku 26 ohlásí. Všimněte si, že v pravém sloupci byla zavolána metoda definovaná ve třídě Matka na @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 337 z 433 338 Myslíme objektově v jazyku Java 1.5 rozdíl od levého sloupce, v němž je volána metoda definovaná ve třídě Dcera. To je ale naprosto správně, protože jsme si přece říkali, že při volání soukromých metod se vždy použijí metody z té třídy, ve které se nachází definice volající metody (vzpomeňte si na naši analogii s interním telefonem v rámci vagónu). Řádky 27 a 28 nám prozradí, že stále pracujeme s podobjektem s názvem Matka_2 a také se stejnou instancí (máme-li se stále pohybovat v jedné třída, tak to ani jinak nejde, že?). Veřejná metoda Na řádku 29 se dozvídáme, že vstupujeme do metody veřejná(). Na tomto řádku je zajímavé právě to, že je v obou sloupcích stejný, což znamená, že se v obou případech volá stejná definici ve stejné třídě, a to přesto, že v levém sloupci je metoda veřejná() volána z metody definované ve třídě Dcera a vpravo z metody definované ve třídě Matka. Tentýž příkaz zavolá vlevo metodu ze stejné třídy a vpravo metodu z jiné třídy. Nepochopitelné je to však pouze pro neznalého. My ale už víme, že v obou případech se posílá ta samá zpráva té samé instanci, takže v obou případech musí dojít k zavolání té samé metody – v našem případě metody veřejná() definované ve třídě Dcera. Instance vnučka Opatrně zkusím předpokládat, že jste vše zhruba pochopili, a proto jsem pro vás připravil jednoduchý úkol: vysvětlete obdobným způsobem shody a rozdíly ve výpisech v levém a pravém sloupci u volání metod pro instanci třídy Vnučka. Vyvolání překryté verze metody V překrývajících dceřiných metodách často potřebujeme použít překrytou rodičovskou verzi definované metody. Velmi častou je např. situace, kdy překrývající metoda nejprve něco připraví a pak zavolá překrývanou rodičovskou verzi nebo naopak zavolá nejprve rodičovskou verzi a pak něco doplní. V takovýchto případech můžeme použít klíčové slovo super (známe je již od konstruktorů), které bychom mohli považovat za takové this pro rodičovský podobjekt. Budeme-li chtít např. překrýt metodu toString() tak, že za text vracený rodičovskou metodou přidáme písmeno D uzavřené v kulatých závorkách, bude její tělo tvořeno příkazem: @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 338 z 433 Kapitola 8: Třídy mohou také dědit 339 return super.toString() + "(D)"; Při používání super bychom měli vědět, že: Použitím super posíláme zprávu rodičovskému podobjektu. Pomocí klíčového slova super proto můžeme volat pouze metody bezprostředního rodiče. Je přitom jedno, jestli jsou tyto metody definovány v rodičovské třídě nebo je třída zdědila. Použití super není možné zřetězit, tj. není možné použít super.super. Není proto možné volat rodičovskou třídou překryté metody prarodiče. Volání rodičovských metod pomocí klíčového slova super je konečné a nemá na něj vliv žádné pozdější překrytí jakékoliv metody. Pro testování vlastností volání rodičovských verzí metod jsem připravil metodu rodiče(), kterou najdete v třídách Dcera a Vnučka, a test, který ji zavolá. V této metodě instance zavolá nejprve „svoji“ a poté rodičovskou verzi metody veřejná() a ve vnuččině verzi zavolá ještě rodičovskou verzi metody rodiče(). 1 public void rodiče() 2 { 3 System.out.println("Vnučka - moje verze metody veřejná():"); veřejná(); 4 5 System.out.println("Vnučka - rodičovská verze metody veřejná():"); super.veřejná(); 6 7 System.out.println("\nVnučka - rodičovská verze metody rodiče():\n"); super.rodiče(); 8 9 } Spuštěním testu Super získáte v terminálovém okně následující výpis: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Vytvářím 1. instanci třídy Matka Vytvářím 2. instanci třídy Matka Vytvářím 1. instanci třídy Dcera Vytvářím 3. instanci třídy Matka Vytvářím 2. instanci třídy Dcera Vytvářím 1. instanci třídy Vnučka Volám dcera.rodiče Dcera - moje verze metody veřejná(): Třída Dcera, metoda veřejná(): Podobjekt: Dcera_1 Instance: Dcera@175078b Dcera - rodičovská verze metody veřejná(): Třída Matka, metoda veřejná(): Podobjekt: Matka_2 @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 339 z 433 340 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 Myslíme objektově v jazyku Java 1.5 Instance: Dcera@175078b Volám vnučka.rodiče Vnučka - moje verze metody veřejná(): Třída Vnučka, metoda veřejná(): Podobjekt: Vnučka_1 Instance: Vnučka@42552c Vnučka - rodičovská verze metody veřejná(): Třída Dcera, metoda veřejná(): Podobjekt: Dcera_2 Instance: Vnučka@42552c Vnučka - rodičovská verze metody rodiče(): Dcera - moje verze metody veřejná(): Třída Vnučka, metoda veřejná(): Podobjekt: Vnučka_1 Instance: Vnučka@42552c Dcera - rodičovská verze metody veřejná(): Třída Matka, metoda veřejná(): Podobjekt: Matka_3 Instance: Vnučka@42552c Předpokládám, že vám nemusím tento výpis podrobně rozebírat a že si v něm potvrzení toho, co jsme si před chvílí řekli, najdete sami. 8.3 Vytváříme dceřinou třídu Uff uff uff – bylo toho hodně, co? Nejvyšší čas přestat si hrát a vytvořit něco trochu praktičtějšího. Zavřete proto projekt s matkou, dcerou a vnučkou a otevřete projekt 08_Dědičnost_tříd_A, v němž se znovu vrátíme k našim geometrickým tvarům. Projekt 08_Dědičnost_tříd_A je výchozím projektem další části této kapitoly. Je to v podstatě projekt, s nímž jsme končili předchozí kapitolu. V jeho třídách jsem provedl jednu drobnou změnu, o níž vám za chvíli povím, a pro zjednodušení jsem z něj odstranil třídy, které jsme v průběhu předchozích kapitol vytvořili a které prozatím nebudeme potřebovat. Třídy, které v průběhu zbytku kapitoly vytvoříme jakož i vzorové podoby všech tříd, které budete mít za úkol vytvořit samostatně, najdete v projektu 08_Dědičnost_tříd_B (nekončí písmenem Z, protože to tentokrát není závěrečný projekt kapitoly). @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 340 z 433 Kapitola 8: Třídy mohou také dědit 341 Obrázek 8.6 Výchozí projekt této kapitoly Jednoduchá dceřiná třída Při úvodním výkladu o rodičovských a dceřiných třídách jsme si jako příklad objektů, pro které je vhodné vytvořit dceřinou třídu, uváděli kruh a čtverec. Pojďme nyní spolu definovat třídu Čtverec. 1. Definujte novou standardní třídu, nazvěte ji Čtverec a otevřete její zdrojový kód. 2. V diagramu tříd přesuňte třídu vpravo vedle třídy Obdélník a natáhněte od ní k obdélníku dědickou šipku. 3. Přesvědčte se, že BlueJ vám opět ušetřil práci s psaním a upravil hlavičku třídy do tvaru public class Čtverec extends Obdélník @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 341 z 433 342 Myslíme objektově v jazyku Java 1.5 4. Projděme si zdrojový kód. Atribut třídy počet může čtverec převzít (= zdědit) od obdélníku. Nepotřebujme, aby si čtverec počítal své instance sám a bude nám stačit, když budou počítány spolu s obdélníky. Můžete tedy ve zdrojovém kódu třídy Čtverec tento atribut smazat. 5. Obdobně může čtverec převzít od obdélníku i atribut pořadí, takže ve čtverci smažete i ten. 6. Ve zdrojovém kódu se dostáváme ke předpřipravenému prázdnému bezparametrickému konstruktoru. Pokud si nepamatujete, jak pracuje obdélníkův bezparametrický konstruktor, zkuste třídu přeložit a požádejte ji ó vytvoření instance. 7. Pokud jste postupovali přesně tak, jak jsem říkal, měl by se teď překladač vzbouřit, že v metodě toString() používáte proměnnou pořadí, ale nemáte ji definovanou. 8. Dohodneme se, že i v tomto případě se spokojíme se zděděnou verzí metody, takže ji můžete klidně smazat a zkusit vše přeložit znovu. Tentokrát by se to mělo podařit. 9. Nechte si vytvořit instanci a podívejte se jí do útrob. Jak uvidíte, její šířka a výška se liší. Nebude nám, než upravit konstruktor tak, aby se vytvořil ten správný obdélník. 10. Zadejte do těla bezparametrického konstruktoru příkaz: super( 0, 0, AP.getKrok(), AP.getKrok() ); Tady vám prozradím, že jsem pro tuto kapitolu změnil ve všech třídách grafických obrazců přístupová práva k jejich statickým atributům AP, v nichž si (stejně jako my ve stromu) uchovávají odkaz na aktivní plátno. Původní private jsem změnil na protected. Budete-li proto vytvářet potomky těchto tříd, můžete tohoto atributu využít. 11. Zkuste nyní opět třídu přeložit a vytvořit její instanci. Pohlédnete-li jí do útrob, už by nám měly hodnoty jejích atributů vyhovovat. Budete-li chtít pádnější důkaz, požádejte o odkaz na aktivní plátno a předejte mu instanci – krásný čtverec, že? 12. Rozbalte nyní seznam metod zděděných z třídy Obdélník. Projděte jej a vytipujte metody, které bude nutno překrýt, protože nám jejich původní podoba nebude vyhovovat. Tak co, které jste vybrali? Já jsem dospěl k závěru, že všechny metody by se nám hodily s výjimkou jediné, a tou je metoda setRozměr(int,int), která umožňuje nastavení různých délek stran a která by nám tak mohla pokazit @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 342 z 433 Kapitola 8: Třídy mohou také dědit 343 čtvercovost našeho čtverce. Hrubou kostru bychom tedy měli hotovou, teď ještě zbývá vymyslet, jak definovat překrývající verzi metody setRozměr(int,int). Můžeme si vybrat ze dvou možných reakcí na zadání různých velikostí stran: „rozčílíme se“ a vyvoláme nějaký chybový stav, upravíme špatně zadané velikosti na takové, které nám budou vyhovovat, a ty pak nastavíme. Obecně není možno říci, která reakce je lepší – záleží na řešené úloze. Já jsem proto vybral druhou možnost, protože je méně konfliktní. Při rozhodování o právě velikosti parametrů máme zase řadu možností. Zvolil jsem tu, při které se nastaví oba rozměry na menší ze zadaných velikostí. K tomu využijeme statickou metodu min(int,int), kterou najdeme v systémové třídě Math. Překrývající verze metody setRozměr(int,int) by tedy mohla mít následující tvar: 1 public void setRozměr( int šířka, int výška ) 2 { 3 int strana = Math.min( šířka, výška ); super.setRozměr( strana, strana ); 4 5 } Doplňte definici této metody a třídu Čtverec vyzkoušejte. Zkuste instanci posouvat jak sami, tak pomocí přesouvače. Zkuste ji pak měnit její rozměry, a to opět jak ručně, tak pomocí kompresoru. Definujte třídu Kruh jako potomka třídy Elipsa. Konstruktory potomka Jedinou věcí, kterou dceřiné třídy nemohou zdědit, jsou konstruktory. Rodičovské konstruktory nemohou použít k ničemu jinému, než ke konstrukci svých podobjektů. Každý konstruktor, který bude dceřiná třída potřebovat, je proto třeba znovu definovat. Definici dceřiných konstruktorů není vhodné odbýt. Pokud se rozhodneme, že třída bude definovat pouze bezparametrický konstruktor a někdy v budoucnu se rozhodneme definovat její potomky, budou tito potomci odkázáni pouze na něj a nebudou již mít možnost využít parametrické konstruktory svých prarodičů. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 343 z 433 344 Myslíme objektově v jazyku Java 1.5 Při návrhu dceřiné třídy je proto třeba se zamyslet nad tím, které konstruktory není možno vynechat, protože by nám mohly při definici případné dceřiné třídy chybět. Vynechat můžeme pouze takové konstruktory, které bychom mohli v případě potřeby bez problému dodefinovat pomocí rodičovských konstruktorů, které budou k dispozici. V případě našich čtverců a kruhů jsou těmito nutnými konstruktory ty, které umožňují zadat pozici, rozměr i barvu vytvářeného obrazce. Ostatní typy konstruktory, které nabízí třídy Obdélník a Elipsa a jejichž ekvivalenty by se nám mohly v budoucnu hodit, je možno z těchto základních konstruktorů v případě potřeby odvodit Doplňte do definic tříd Čtverec a Kruh konstruktory, které umožňují zadat jejich rozměr, pozici a barvu. Doplňte pak ekvivalenty ostatních konstruktorů nabízených třídami Obdélník a Elipsa. Složitější dceřiná třída Účelem tříd Čtvrec a Kruh bylo definovat obrazec, který je schopen zabezpečit nějakou dodatečnou funkčnost – v našem případě zaručit, aby oba rozměry obrazce byly shodné. To bylo opravdu jednoduché rozšíření a k jeho dosažení nám stačilo překrýt jedinou metodu předka. Podívejme se nyní na maličko rafinovanějšího potomka, u nějž budeme muset k dosažení požadovaných vlastností sáhnout trochu hlouběji do definic jeho metod a na kterém si pak budeme moci znovu ověřit platnost některých zákonitostí, o nichž jsme si povídali v prvých dvou podkapitolách této kapitoly. Definujme třídu XObdélník, jejíž instance budou obdélníky s viditelnými úhlopříčkami, které zruší pravidlo, že jejich souřadnice budou souřadnicemi levého horního rohu, a budou za svoje souřadnice považovat souřadnice průsečíku svých úhlopříček. Projdeme si spolu definici této třídy krok za krokem a opět si připomeneme, jak bychom měli v takovýchto případech postupovat. Příprava prázdné třídy a testovací třídy Začneme tím, že vytvoříme novou standardní třídu a k ní hned vytvoříme příslušnou testovací třídu a v ní připravíme základní testy. V řadě případů je nejrychlejší netancovat kolem přípravku, vytváření instancí a vyvolávání potřebných metod a připravit testy ručně. Pak totiž můžete v testech použít i metody, které jste v testované třídě ještě nedefinovali. Testovací třídu se @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 344 z 433 Kapitola 8: Třídy mohou také dědit 345 pak sice někdy nepodaří hned přeložit, ale to nevadí, protože tím máte definovaný první úkol: definovat potřebně metody, aby se testovací třída přeložila. Protože vím, že vytváření testů je pracné a vyžaduje jistou představu o tom, co má vlastně testovaná třída umět, tak jsem ji pro vás již připravil – najdete ji (dle očekávání) v projektu 08_Dědičnost_tříd_B. Třídu XObdélník z něj ale nestahujte, tu budeme vytvářet od počátku pěkně krok za krokem. Vytvořte tedy novou standardní třídu a pojmenujte ji XObdélník. Před tím, než budete číst dál, projděte její zdrojový kód a rozmyslete si, které z předpřipravených atributů a metod bude výhodné ponechat a které bude vhodné smazat. Tak co, vymyšleno? Já bych to udělal naprosto stejně jako se čtvercem a kruhem – ponechal bych pouze prázdný bezparametrický konstruktor. Po této úpravě ale nepůjde přeložit testovací třídu, protože ta vyžaduje vedle bezparametrického konstruktoru ještě konstruktory se čtyřmi a pěti parametry. Nadefinujeme prozatím prázdné, aby se testovací třídu podařilo přeložit. Definice konstruktorů Nejlepší bude začít definicí nejobecnějšího pětiparametrického konstruktoru, protože zbylé dva jej budou stejně volat s nějakými implicitními hodnotami parametrů. Je zřejmé, že tento konstruktor bude začínat voláním rodičovského pětiparametrického konstruktoru. Rozmyslete si, jaké bude muset zadat tomuto konstruktoru parametry, když víte, že pro náš konstruktor jsou zadávané souřadnice souřadnicemi středu vytvářeného obrazce, kdežto pro rodičovský konstruktor jsou souřadnicemi jeho levého horního rohu. Na obdélníku mají být nakresleny jeho úhlopříčky – budeme tedy potřebovat dva atributy, kam uložíme odkazy na příslušné čáry (atributy nejlépe konstantní, protože čáry se již nebudou měnit). Po zavolání rodičovského konstruktoru pak musíme tyto atributy inicializovat. Zkuste nyní definovat svoji vlastní verzi těla pětiparametrického konstruktoru a pak si ji porovnejte s následující vzorovou definicí. 1 public XObdélník( int x, int y, int šířka, int výška, Barva barva ) 2 { super( x-šířka/2, y-výška/2, šířka, výška, barva ); 3 4 //Pomocné proměnné mohu bohužel definovat až nyní, //protože před voláním rodičovského kontruktoru nesmí být žádný příkaz 5 6 int x0 = x - šířka/2; @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 345 z 433 346 Myslíme objektově v jazyku Java 1.5 7 8 9 10 } int y0 = y - výška/2; hlavní = new Čára( x0, y0, x0+šířka, y0+výška ); vedlejší = new Čára( x0, y0+výška, x0+šířka, y0 ); Dohodneme-li se, že implicitní pozice obdélníka bude v levém horním rohu a že jeho implicitní barva bude červená, pak budou jasné i definice zbylých konstruktorů. Zkuste si je nadefinovat a své definice si porovnejte s následujícími: 1 2 3 4 5 6 7 8 9 public XObdélník() { this( AP.getKrok(), AP.getKrok(), 2*AP.getKrok(), AP.getKrok() ); } public XObdélník( int x, int y, int šířka, int výška ) { this( x, y, šířka, výška, Barva.ČERVENÁ ); } Konstruktory jsou nadefinovány, můžete požádat testovací třídu, aby naplnila zásobník odkazů z přípravku. Metoda kresli(java.awt.Graphics2D) Zásobník odkazů se sice naplní, ale vykreslení naších obdélníků není dokonalé – chybí jim požadované přeškrtnutí úhlopříčkami. Jak vás asi napadne, potřebujeme překrýt metodu kresli(java.awt.Graphics2D). Její definice je ale jednoduchá a předpokládám, že vás napadne i bez nahlížení do vzorového řešení: 1 public void nakresli( java.awt.Graphics2D kr ) 2 { 3 super .nakresli( kr ); 4 hlavní .nakresli( kr ); 5 vedlejší.nakresli( kr ); 6 } Naplnění zásobníku odkazů by mělo již proběhnout bez problémů. Můžeme tedy spustit první test – zkuste např. test posunů. Metoda setPozice(int,int) Jestli postupujete krok za krokem se mnou, zaznamenali jste, že se sice posunul podkladový obdélník, avšak neposunuly se příslušné úhlopříčky. V první etapě by vás možná napadlo překrýt parametrické posunové metody a nechat v nich posunout jako obdélník, tak úhlopříčky podobně, jako jsme je v @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 346 z 433 Kapitola 8: Třídy mohou také dědit 347 poslední metodě nechávali nakreslit.Dopředu vám však prozradím, že byste stejně za chvíli narazili. Zkrátíme proto naše objevování správného řešení a navedu vás na ně rovnou. Podíváte-li se do definice třídy Obdélník, zjistíte, že posunové metody jsou naprogramovány téměř stejně, jako jsme je definovali pro náš strom: využívají metody setPozice(int,int). Bude tedy nejlepší upravit ji. Zkuste to a svoji metodu pak vyzkoušejte spuštěním posunových testů. Kdybyste měli problémy, můžete si svoje řešení porovnat s následujícím: 1 public void setPozice( int x, int y ) 2 { int s2 = getŠířka() / 2; 3 4 int v2 = getVýška() / 2; super.setPozice( x-s2, y-v2 ); 5 6 hlavní.setPozice( x-s2, y-v2 ); 7 vedlejší.setPozice( x-s2, y+s2 ); 8 } Tak co? Předpokládám, že vám vyšel stejný výsledek jako mně: obdélník se místo doprava posunul nahoru (viz obr. 8.7). Obrázek 8.7 Úprava metody nakresli(java.awt.Graphics2D) ukázala další chybu Předpokládám, že nemáte představu, čím by to mohlo být. V takových chvílích bývá někdy nejlepší sledovat činnost programu krok za krokem. Otevřete proto zdrojový kód testovací třídy a vložte do něj zarážku do metody testPosuny() na řádek, kde se v volá metoda posunVprav(). Nyní znovu spusťte test posunů: @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 347 z 433 348 Myslíme objektově v jazyku Java 1.5 1. Nejprve se objeví dialogové okno, které nám oznámí, že přijde posun vpravo. Stiskněte ANO. 2. Otevře se okno debuggeru a současně zdrojový kód testovací tříd s ukazatelem na nastavené zarážce. Požádejte o krok dovnitř. 3. Otevře se zdrojový kód třídy Obdélník a ukazatel se nastaví na jediný příkaz těla metody posunVpravo(), kterým je volání její jednoparametrické verze. Nyní bychom sice mohli požádat znovu o kro dovnitř, ale to bychom nejprve vběhli do metody getKrok() ve třídě AktivníPlátno, z ní bychom se vrátili zpět sem a teprve pak bychom se přesunuli tam, kam nás srdce táhne. Abychom si tento mezikrok ušetřili, využijeme toho, že metoda, kam směřujeme, je definována hned vedle. Umístíme do jejího těla zarážku a dáme debuggeru příkaz Pokračovat. 4. Zastavili jsme se na jediném příkazu těla metody posunVpravo(int), kterým je příkaz setPozice( xPos+vzdálenost, yPos ); Podívejme se nejprve, jaké mu budeme předávat parametry. V okně debuggeru zjistíme, že xPos=0, vzdálenost=50 a yPos=25. Žádáme tedy, aby nám tato metoda přesunula obdélník na souřadnice [50;25]. Jak ale víme nečiní tak. Budeme proto opět krokovat dovnitř, abychom zjistili, proč tak nečiní. 5. Otevře se znovu okno se zdrojovým kódem třídy XObélník a ukazatel se nastaví na první příkaz metody setPozice(int,int). A už by nám mělo pomalu začít svítat. Podíváte-li se na hodnoty parametrů, vidíte že je po metodě požadováno, aby přesunula svůj objekt na pozici [50;25]. Jenomže souřadnice xobdélníků se počítají vůči jejich středu, takže bychom měli po této metodě požadovat, aby svůj obrazec přesunula na pozici [100,50]. Prohlédnete-li si tělo metody, uvidíte, že tyto už dopředu zmenšené zadané souřadnice ještě dále zmenšujeme, takže není divu, že se nám výsledný obrazec posouvá jinam než potřebujeme. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 348 z 433 Kapitola 8: Třídy mohou také dědit 349 Jak přesvědčit objekt, aby se pokaždé choval jinak Následující úvaha je natolik důležitá, že jsem si ji dovolil vypíchnout do samostatného rámečku. Doufám, že se mi ji podaří vysvětlit dostatečně průzračně. Potřebujeme vymyslet, jak nevhodné chování xobdélníků vyléčit. Překrývající metoda setPozice(int,int) dostává od volající metody evidentně špatné parametry. Musíme se tedy podívat do volající metody, jak to zlepšit. Ve volající metodě posunVpravo(int) jsme ale již byli a tenkrát jsme byli s podobou předávaných parametrů spokojeni. Jak to, že ve volané metodě s nimi spokojeni nejsme? Je to proto, že ve volající metodě jsme si mysleli (špatně), že nastavujeme souřadnice obdélníku a až ve volané metodě jsme si uvědomili, že se vlastně nastavují souřadnice xobdélníku. Potřebovali bychom tuto informaci předat také „o patro výš“. Jediný příkaz volající metody chce po volané metodě, aby přemístila objekt o vzdálenost doprava a vypočítává jí požadované souřadnice. Počítá je však z hodnot atributů xPos a yPos, ve kterých jsou uloženy souřadnice levého horního rohu obdélníku. Kdybychom místo přímého výběru hodnoty souřadnic z těchto atributů požádali instanci, ať nám své souřadnice prozradí, prozradil by nám obdélník souřadnice svého levého horního rohu kdežto xobdélník by nám vrátil souřadnice svého středu. Když tedy v metodě nahradíme výběr hodnot z atributů xPos a yPos voláním metod getX() a getY(), získáme od obdélníku stejná čísla, jako před tím. Pokud pak tyto metody ve třídě XObdélník vhodně překryjeme, začnou nám vracet souřadnice středu xobdélníku, a to je právě to, co potřebujeme. Při těchto úpravách ale nesmíme zapomenout ani na ostatní metody, které potřebují pracovat se souřadnicemi a získávají je přím z atributů. S atributy budou od této chvíli pracovat již pouze konstruktory a metody, které jejich hodnoty zjišťují a nastavují. Všichni ostatní začnou místo atributů používat příslušné přístupové metody, čímž umožní potomkům změnit význam souřadnic. Všechny metody, v nichž bychom tuto úpravu neprovedli, bychom jinak museli překrýt. Nahrazení přímého oslovení atributů zavoláním jejich přístupové metody nám umožní tyto metody zdědit, aniž bychom se o ně museli dále starat. 6. Upravte ve třídě Obdélník v metodě posunVpravo(int) její jediný příkaz do tvaru: setPozice( getX()+vzdálenost, getY() ); @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 349 z 433 350 Myslíme objektově v jazyku Java 1.5 7. Definujte ve třídě XObdélník překryté verze metod getX() a getY(): public int getX() { return super.getX() + getŠířka()/2; } public int getY() { return super.getY() + getVýška()/2; } 8. Projděte zdrojový kód třídy Obdélník a upravte ostatní místa, kde je vhodné změnit přímé „oslovení“ atributu voláním příslušné přístupové metody. Napovím vám, že to bude v metodách getPozice(), getOblast(), posunDolů(int) a toString(). Někteří z vás možná namítnou, že bychom měli upravit také metodu nakresli(java.awt.Graphics2D), která atributy xPos a yPos také používá, ale to bychom neudělali dobře. Tato metoda má správně nakreslit právě obdélník a má jej nakreslit přesně na tom místě, kam patří. Proto je správné, že pro zadání jeho pozice používá atributy, které hovoří právě o něm a ne o jeho potomcích. Z toho samozřejmě zákonitě vyplývá, že kdykoliv budeme v budoucnosti od obdélníku odvozovat nějakého potomka, budeme určitě muset tuto metodu upravit, a to i tehdy, pokud instance tohoto potomka zůstanou obyčejnými obdélníky, které budou mít pouze jiný vztažný bod, vůči němuž se budou počítat jejich souřadnice. Tato otázka je daleko důležitější, než na první pohled vypadá, a její zodpovězení má poměrně závažné důsledky. Proto se k ní ještě vrátím. 9. Vyzkoušejte test posunů a ověřte, že vše pracuje tak, jak má. 10. Vyzkoušejte i testy přesouvače. 11. Vyzkoušejte test správné implementace IHýbací, v němž je použit směrovatelný kompresor. Poslední test nemůže chodit, protože jsme neudělali s nastavováním rozměru totéž, co s nastavováním pozice. Zkuste proto doprovodit třídu do cíle sami. Kontrolní řešení naleznete v projektu 08_Dědičnost_tříd_B. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 350 z 433 Kapitola 8: Třídy mohou také dědit 351 Samostatná úloha: Terč Abyste si definici rafinovanějších dceřiných tříd procvičili, připravil jsem pro vás ještě jeden příklad. Definujte třídu Terč, jejíž instance nakreslí na plátno tři soustředné kruhy přeškrtnuté záměrným křížem (viz obr. 8.8). Navíc bude, stejně jako předchozí třída XObdélník, definovat pozici svých instancí jako pozici společného středu soustředných kruhů a průsečíku os záměrného kříže. Pro ty, kteří budou mít s úlohou problémy, jsem opět připravil vzorové řešení bez dokumentačních komentářů (použité oddělovací komentáře jsem pro lepší orientaci ve zdrojovém kódu nechal). To s komentáři si můžete zkopírovat z projektu 08_Dědičnost_tříd_B. Obrázek 8.8 Terč public class Terč extends Kruh { //== KONSTANTNÍ ATRIBUTY TŘÍDY ================================================= private static final Barva B1 = Barva.ŽLUTÁ; private static final Barva B2 = Barva.MODRÁ; private static final Barva B3 = Barva.ČERVENÁ; //== KONSTANTNÍ ATRIBUTY INSTANCÍ ============================================== private private private private final final final final Kruh Kruh Čára Čára mezi; střed; vodor; svislá; @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 351 z 433 352 Myslíme objektově v jazyku Java 1.5 //############################################################################## //== KONSTRUKTORY A METODY VRACEJÍCÍ INSTANCE VLASTNÍ TŘÍDY ==================== public Terč() { this( AP.getKrok()/2, AP.getKrok()/2, AP.getKrok() ); } public Terč( int x, int y, int průměr ) { this( x, y, průměr, B1, B2, B3 ); } public Terč( Pozice počátek, int průměr ) { this( počátek.x, počátek.y, průměr ); } public Terč( Pozice počátek, int průměr, Barva barva1, Barva barva2, Barva barva3 ) { this( počátek.x, počátek.y, průměr, barva1, barva2, barva3 ); } public Terč( Oblast oblast ) { this( oblast.x, oblast.y, Math.min(oblast.šířka, oblast.výška) ); } public Terč( Oblast oblast, Barva barva1, Barva barva2, Barva barva3 ) { this( oblast.x, oblast.y, Math.min(oblast.šířka, oblast.výška), barva1, barva2, barva3 ); } public Terč( int x, int y, int průměr, Barva barva1, Barva barva2, Barva barva3 ) { super( x-průměr/2, y-průměr/2, průměr, barva1 ); mezi = new Kruh( 0, 0, 0, barva2 ); střed = new Kruh( 0, 0, 0, barva3 ); vodor = new Čára( 0, 0, 0, 0 ); svislá= new Čára( 0, 0, 0, 0 ); } setRozměr( průměr ); //== PŘÍSTUPOVÉ METODY ATRIBUTŮ INSTANCÍ ======================================= public int getX() { return super.getX() + getŠířka()/2; } public int getY() { @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 352 z 433 Kapitola 8: Třídy mohou také dědit } 353 return super.getY() + getVýška()/2; public void setPozice( int x, int y ) { int r = getŠířka() / 2; int s = r / 3; AP.nekresli(); super .setPozice( x-r, y-r mezi .setPozice( x-2*s, y-2*s střed .setPozice( x-s, y-s vodor .setPozice( x-r, y svislá.setPozice( x, y-r AP.vraťKresli(); } ); ); ); ); ); public void setRozměr( int šířka, int výška ) { int průměr = Math.min( šířka, výška ); int p2 = průměr / 2; int p3 = průměr / 3; int x = getY(); int y = getY(); AP.nekresli(); //Nejprve nastavíme všechny rozměry super.setRozměr( průměr, průměr ); mezi .setRozměr( 2*p3 ); střed.setRozměr( p3 ); //A pak všechny hormadně přesuneme setPozice( x, y ); } //Osový kříž nestačí přesunout, protože se mohl změnit jeho rozměr vodor .spoj( x-p2, y, x+p2, y ); svislá.spoj( x, y-p2, x, y+p2 ); AP.vraťKresli(); //== PŘEKRYTÉ KONKRÉTNÍ METODY RODIČOVSKÉ TŘÍDY ================================ public void nakresli( java.awt.Graphics2D kr ) { super .nakresli( kr ); mezi .nakresli( kr ); střed .nakresli( kr ); vodor .nakresli( kr ); svislá.nakresli( kr ); } }//public class Terč @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 353 z 433 354 8.4 Myslíme objektově v jazyku Java 1.5 Vytváříme rodičovskou třídu Na příkladu čtverce a kruhu jste názorně viděli jeden z velmi oceňovaných přínosů dědičnosti, kterým je odstranění nutnosti zdvojeného psaní stejného kódu. Čtverec a kruh většinu metod, které zdědily, ponechaly beze změny, protože jim jejich funkčnost vyhovovala, takže jste pro ně nemuseli definovat jejich vlastní verze metod a ušetřili jste si tak spoustu psaní. Tady ale nejde pouze o to, jestli ušetříte nějaké to ťuknutí do klávesnice, ale především o to, že kód metod, které jsou pro obě třídy stejné, je soustředěn na jednom místě, takže když se později rozhodnete jej z nejrůznějších důvodů změnit, nemusíte přemýšlet nad tím, kam všude musíte kvůli této změně sáhnout. Snaha po sdružení stejných či podobných metod a zabránění zdvojování stejného kódu bývá jedním z hlavních důvodů definice tříd, které jsou společným rodičem několika do té doby samostatných tříd. Typickým příkladem skupiny tříd, která si přímo koleduje o společného rodiče, jsou právě naše grafické třídy. Podíváte-li se na portfolia jejich metod, zjistíte, že všechny implementují metody požadované rozhraním IPosuvný a spolu s nimi další řadu posunových metod. Prohlédnete-li si ve zdrojových souborech těla těchto metod, zjistíte navíc, že jsou prakticky stejná. Bylo by proto užitečné pro definovat všechny posuvné třídy společnou rodičovskou třídu a nazvat ji třeba Posuvný. V ní bychom definovali všechny metody, které by měl správný posuvný prvek obsahovat a které by pak mohli potomci této třídy zdědit. V druhém kroku bychom pak upravili všechny posuvné třídy tak, že bychom je definovali jako potomky tohoto rodiče a metody, které mohou od rodiče zdědit, bychom z jejich těl odstranili. Společný rodič Posuvný Pojďme si to vyzkoušet na třídách našeho projektu. Půjdeme spolu opět krok za krokem. Příprava 1. Vytvořte novou standardní třídu, pojmenujte ji Posuvný a otevřete okno s jejím zdrojovým kódem. 2. Natáhněte k právě vytvořené třídě dědické šipky od tříd implementujících rozhraní IPosuvný a přidávajících k jím požadovaným metodám další posunové metody, tj. od tříd Obdélník, Elipsa, Trojúhelník, Čára, Text, Strom a od vaší třídy, kterou jste vytvořili ve druhé kapitole a postupně vylepšovali (má@Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 354 z 433 Kapitola 8: Třídy mohou také dědit 355 te-li jich víc, tak od všech). Implementační šipky k rozhraní IPosuvný však zatím nemažte. Tyto třídy budu ve zbytku této podkapitoly označovat jako IP-třídy. Není to však žádný oficiální název. Je to pouze pracovní název pro skupinu tříd, na kterou se budu často odvolávat. Možná se podivujete, proč jsem do seznamu nezařadil i třídy Výtah a Kabiny, které také implementují rozhraní IPosuvný. Hlavním úkolem instancí těchto tříd však není definovat a následně zobrazit nějaké geometrické tvary či obrázky. Jejich instance mají předem zadané úkoly a implementují rozhraní jen proto, aby mohly tyto úkoly splnit. Nebylo by proto vhodné, kdyby ovlivňovaly naše rozhodování o optimální podobě společné rodičovské třídy posuvných geometrických tvarů a obrázků. Až budeme mít třídu Posuvný navrženou, můžeme se dodatečně rozhodnout, zda tyto třídy definujme jako její potomky, anebo zda zůstaneme u dosavadního stavu, kdy budou třídy přímými potomky třídy Object, které pouze implementují rozhraní IPosuvný. 3. Otevřete soubory se zdrojovými kódy právě označených dceřiných tříd (IP-tříd) a uspořádejte si je nějak rozumně na obrazovce, ať mezi nimi můžete snadno přepínat (nejlepší je uspořádat je do kaskády). Šablona standardní prázdné třídy rozděluje řádkovými komentáři zdrojový soubor do sekcí. V následujících krocích budeme procházet zdrojovým kódem těchto tříd sekci za sekcí a v každé sekci popřemýšlíme, které z jejích atributů či metod bylo vhodné přestěhovat do společné rodičovské třídy. V druhém kole pak budeme procházet jednotlivé dceřiné třídy a členy, které jsme zařadili do rodičovské třídy, z dceřiné třídy buď odstraníme, nebo je překryjeme vlastní verzí. Zkuste nejprve projít sami např. zdrojovým kódem třídy Obdélník a poznamenejte si, co byste do společné rodičovské třídy přestěhovali. Pak se vraťte k následujícímu textu a porovnejte moje řešení se svým. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 355 z 433 356 Myslíme objektově v jazyku Java 1.5 Konstantní atributy třídy V první sekci jsou konstantní atributy třídy. Všechny třídy zde deklarují atribut AP, v němž uchovávají odkaz na aktivní plátno. Bylo by proto logické přestěhovat tento atribut do rodičovské třídy Posuvný a ze zdrojových kódů jednotlivých tříd jej odmazat. Aby mohly dceřiné třídy k atributu přistupovat, nesmíme jej deklarovat jako soukromý. Pro naše účely bude optimálně jej deklarovat jako chráněný (protected). /** Aktivní plátno, které dohlíží na správné vykreslení instance. */ protected static final AktivníPlátno AP = AktivníPlátno.getPlátno(); Proměnné atributy třídy Další sekce je vyhrazena proměnným atributům třídy. Zde nám šablona standardní třídy nabízí deklaraci atributu počet, který bude počítat vytvořené instance. Tento atribut mají všechny IP-třídy. Dohodněme se proto, že budeme počítat všechny vytvořené tvary společným počitadlem (beztak pořadí instance využívám prakticky jen při ladění), které bude atributem jejich společné rodičovské třídě. Na rozdíl od atributu AP ale ponechte počitadlo jako soukromé. Nestojíte přece o to, aby jeho hodnotu nějaká dceřiná třída nepředvídatelně měnila. /** Celkový počet vytvořených instancí. */ private static int počet = 0; Konstantní atributy instancí Pokračujme konstantními atributy instancí, kde nám šablona standardní třídy nabízí celočíselný atribut počet, který v deklaraci hned také inicializuje. I tento atribut necháme deklarovat rodičovskou třídu, avšak označíme jej jako protected, protože by se přístup k němu mohl instanci hodit a navíc jej instance beztak nemůže změnit. /** Rodné číslo instance = jako kolikátá byla vytvořena. */ protected final int pořadí = ++počet; Definujte jej proto ve třídě Posuvný a v druhém kole jej v definicích jejích dceřiných tříd smažeme. IP-třídy mají v této sekci uveden atribut název, v němž si jejich instance uchovávají svůj název. I tento atribut bude nejlepší umístit do společné rodičovské třídy. Umístěte jej tam a poznamenejte si, že na něj nesmíme zapomenout v konstruktoru, protože tento atribut není (na rozdíl od svých předchůdců) v deklaraci inicializován. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 356 z 433 Kapitola 8: Třídy mohou také dědit 357 /** Název sestávající z názvu třídy a pořadí instance */ private final String název; Proměnné atributy instancí Poslední atributovou sekcí je sekce proměnných atributů instancí. Ta před nás postaví první větší dilema. Doposud byly atributy, které jsme „stěhovali do rodičovské třídy“ součástí všech dceřiných tříd. Atributy xPos a yPos, v nichž si instance některých pamatují svoji polohu, však již všechny IP-třídy nezavádějí. Instance všech IP-tříd musejí umět sdělit svoji polohu. Instance některých tříd ji uchovávají ve speciálních atributech, instance jiných tříd takovéto atributy nezavádějí a svoji polohu v případě potřeby nějakým způsobem na poslední chvíli zjistí. Je na nás, zda se rozhodneme, že pro tuto polohu definujeme atributy ve společné rodičovské třídě, nebo jestli necháme na rozhodnutí tvůrce dceřiné třídy, zda pro zapamatování polohy použije nějaký reálný atribut, jak to dělá např. Obdélník, Elipsa a Trojúhelník, anebo zda zavede polohu jako fiktivní atribut dostupný pouze přes přístupové metody, jak to dělá např. Strom.. V našem příkladu dáme přednost reálným atributům – každý posuvný objekt si bude pamatovat svoji polohu v atributech xPos a yPos. Deklarujeme proto tyto atributy ve třídě Posuvný a odstraníme je z IP-tříd, ve kterých jsou použity. Je na našem rozhodnutí, zda tyto atributy deklarujeme jako soukromé či jako chráněné. Budou-li deklarovány jako soukromé, budou bezpečnější před nechtěnými zásahy tvůrců dceřiných tříd, budou-li deklarovány jako chráněné, budou definice některých metod maličko jednodušší. V praxi se dává většinou přednost bezpečnosti, takže i my deklarujeme tyto atributy jako soukromé. private int xPos; private int yPos; //Bodová x-ová souřadnice počátku //Bodová y-ová souřadnice počátku Vedle toho, že budeme počítat s tím, že v druhém kroku tyto atributy z dceřiných tříd odstraníme, tak si i zde musíme poznamenat, abychom je v konstruktoru nezapomněli inicializovat. Některé IP-třídy mají v této sekci definovány ještě další atributy, výšku, šířku a barvu. Protože se však tyto atributy na posunovatelnosti objektu nijak přímo nepodílejí, nebudeme je přesouvat a ponecháme je v jejich původních třídách. Konstruktory Metody třídy IP-třídy nemají, tak se vrhneme rovnou na konstruktory. U společných rodičovských tříd se většinou nepředpokládá, že aplikace bude vytvářet jejich samostatné instance. Instance společných rodičovských tříd bývají většinou @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 357 z 433 358 Myslíme objektově v jazyku Java 1.5 pouze podobjekty instancí jejich dceřiných tříd. Z toho můžeme vycházet i při rozhodování o tom, které typy konstruktorů definujeme. Z předchozích kroků máme poznamenáno, že musíme inicializovat atributy xPos, yPos a název. Bylo by tedy vhodné definovat konstruktor, který umístí objekt na zadanou pozici. Definovat zbylé verze konstruktorů je zbytečné, protože se tím tvorba konstruktorů dceřiných tříd nijak nezjednoduší. Pro tvorbu názvu zvolíme oblíbenou metodu jeho sestavení z názvu třídy následovaného podtržítkem a pořadím vytvoření instance. Využijeme k tomu metody názevTřídy(Object), která je statickou metodou třídy P a která vrátí název třídy svého parametru. 1 public Posuvný( int x, int y ) 2 { 3 xPos = x; 4 yPos = y; název = P.názevTřídy(this) + "_" + ++počet; 5 6 } Protože tento konstruktor bude zároveň jediným konstruktorem společné rodičovské třídy, musíme si poznamenat, že všechny konstruktory dceřiných tříd, které nepředávají hned řízení jinému konstruktoru stejné třídy, musí na počátku své definice volat tento rodičovský konstruktor. Zároveň si poznamenáme, že máme z konstruktorů dceřiných tříd odstranit inicializace atributů xPos, yPos a název, leda bychom jim potřebovali přiřadit jiné počáteční hodnoty, než které jim přiřazuje rodičovská třída. Metody instancí Nebudeme zde rozlišovat, ve které sekci se ta která metoda nachází. Do rodičovské třídy přesuneme ty metody, pro jejichž definici má rodičovská třída dostatek informací, a to jak přímých, nebo „zprostředkovaných“ prostřednictvím překrytí metod. V našem příkladu budeme přesouvat metody mající na starosti pozici a posun dané instance, tj. následující metody: public public public public public public public public public public public int getX() int getY() Pozice getPozice() void setPozice(int x, int y) String getNázev() String toString() void posunVpravo(int vzdálenost) void posunVpravo() void posunVlevo() void posunDolů(int vzdálenost) void posunDolů() @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 358 z 433 Kapitola 8: Třídy mohou také dědit 359 public void posunVzhůru() Většinu uvedených metod můžeme do třídy Posuvný vložit beze změny jejich těla. Jedinou výjimkou je metoda toString(), která ve výstupním řetězci vrací i šířku, výšku a barvu objektu. O těch však třída Posuvný nemůže nic vědět. Zkrátíme proto definici metody a poznamenáme si, že v dceřiných třídách budeme muset doplnit informace o atributech, i nichž rodičovská třída neví. 1 public String toString() 2 { 3 return název + ": x=" + xPos + ", y=" + yPos; 4 } Všechny uvedené metody budou od této chvíle definovány na jednom místě. Tím se výrazně zefektivní jejich případná následná úprava. Co je však v mnohých případech důležitější: budou-li nám stačit jejich zděděné verze, nebudeme je muset v dalších dceřiných třídách definovat. Vezměte tyto metody např. z elipsy nebo obdélníku a vložte je do kódu třídy Posuvný a upravte příslušně tělo metody toString(). Všechny třídy, pro něž definujeme společného rodiče, mají definovánu také metodu nakresli(java.awt.Graphics2D). Metodu sice mají stejnou, ale v každé třídě je definována jinak. Proto ji prozatím do rodičovské třídy neumisťujeme. Doladění dceřiných tříd Rodičovskou třídu tedy máme připravenou – můžete ji zkusit hned přeložit. Nyní musíme projít všechny její dceřiné třídy a jejich podobu doladit. Z předchozích kroků máme poznamenáno, že v každé z nich máme: odstranit atributy deklarované v rodičovské třídě, upravit definici konstruktoru, aby neinicializovala přesunuté atributy a místo toho na svém počátku zavolala správný konstruktor rodičovské třídy, odstranit nebo překrýt metody definované v rodičovské třídě. Elipsa, Obdélník, Trojúhelník Tyto třídy jsou si nesmírně podobné, tak je proberu najednou. Odstranění přesunutých atributů nečiní potíže. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 359 z 433 360 Myslíme objektově v jazyku Java 1.5 Většina konstruktorů je definována tak, že volají jiný konstruktor téže třídy. Jedinou výjimkou je konstruktor s největším počtem parametrů, který např. u elipsy získá podobu: 1 public Elipsa( int x, int y, int šířka, int výška, Barva barva ) 2 { 3 super( x, y ); this.šířka = šířka; 4 5 this.výška = výška; 6 this.barva = barva; 7 } U obdélníku bude definován stejně a pouze u trojúhelníku bude bohatší o nastavení zadaného směru. Přístupové metody k atributům pozice i posunové metody jsou u všech tří tříd totožné s metodami, které jsme vkládali do rodičovské třídy, takže je můžeme bez obav z těla těchto dceřiných tříd odstranit. Jedinou odchylkou je metoda toString(), kterou musíme překrýt modifikovanou verzí, v níž za řetězec vrácený rodičovskou verzí přidáme informace o šířce, výšce a barvě objektu (u trojúhelníku navíc o jeho směru). Její verzi pro trojúhelník ukazuje následující program, verze pro elipsu a obdélník se liší pouze tím, že nevypisují žádný směr. 1 public String toString() 2 { 3 return super.toString() + ", šířka=" + šířka + ", výška=" + výška + ", barva=" + barva + ", směr=" + směr; 4 5 } Čára Třída Čára je velice podobná předchozím třídám. Nemá sice atributy uchovávající její výšku a šířku, ale uchovává místo nich souřadnice svého druhého koncového bodu. Postup její úpravy proto bude prakticky stejný a nebudu se zde o něm podrobněji rozepisovat. Text Třída Text sice na první pohled vypadá skoro stejně jako předchozí čtyři, nicméně z našeho hlediska má jednu odchylku, kterou nesmíme přehlédnout. U této třídy je atribut název definován jinak než u jejích kolegyň: její instance do tohoto atributu ukládají vypisovaný text. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 360 z 433 Kapitola 8: Třídy mohou také dědit 361 Atribut název je soukromý, takže k němu nemůžeme. Nám by ale nepomohlo ani kdyby byl chráněný či veřejný, protože je deklarován jako konstanta, takže jednou získaný obsah se již nedá změnit. Nejjednodušším řešením tohoto problému je definovat svůj vlastní atribut (může se klidně jmenovat stejně), inicializovat jej v konstruktoru a překrýt metody getNázev() a toString() vlastními verzemi: private final String název; public Text( String text, int x, int y, Barva barva ) { super( x, y ); this.název = text; this.barva = barva; this.font = new Font( "Dialog", Font.BOLD, 12 ); } public String getNázev() { return název; } public String toString() { return název; } Strom Zbývá nám naše třída Strom.. Ta je oproti předchozím třídám trochu nestandardní, protože si např. neuchovává pozici v atributech a i nastavování pozice je u ní složitější. Probereme si proto jednotlivé požadované úpravy postupně: Odstranění atributů deklarovaných v rodičovské třídě se dotkne pouze atributu název, protože atributy xPos a yPos třída vůbec nezavádí. Když už je ale od rodičovské třídy podědí, mohli bychom popřemýšlet nad tím, jestli toho nemůžeme někde využít. Úprava definice konstruktoru bude pro strom spočívat pouze v přidání volání rodičovského konstruktoru. Upravovaný konstruktor pak bude mít tvar: 1 public Strom_8(int x, int y, int šířka, int výška, 2 int podílŠířkyKmene, int podílVýškyKmene) 3 { super( x, y ); 4 5 this.podílVýškyKmene = podílVýškyKmene; 6 this.podílŠířkyKmene = podílŠířkyKmene; 7 AP.nekresli(); @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 361 z 433 362 8 9 10 11 12 } Myslíme objektově v jazyku Java 1.5 koruna = new Elipsa ( x, y, 1, 1, Barva.ZELENÁ ); kmen = new Obdélník( x, y, 1, 1, Barva.ČERVENÁ ); setRozměr( šířka, výška ); AP.vraťKresli(); Odstranění či překrytí zděděných metod nám ale dá trochu přemýšlení, protože např. všechny přístupové metody jsou definovány jinak než v ostatních P-třídách. Tady ale právě využijeme toho, že rodičovský konstruktor uložil pozici do příslušných atributů a můžeme proto ponechat zděděnou verzi metod getX() a getY(), která vrací hodnoty uložené v těchto atributech. Jedinou metodu, kterou budeme muset ve třídě Strom překrýt, je metoda setPozice(int,int), ve které potřebujeme příslušně přesunout korunu a kmen. Chceme-li dále využívat hodnot uložených v atributech xPos a yPos, nesmíme je zapomenout při té příležitosti nastavit. 1 public void setPozice(int 2 { AP.nekresli(); 3 super. setPozice( 4 5 koruna.setPozice( kmen .setPozice( 6 7 AP.vraťKresli(); 8 9 } x, int y) x, x, x y y y + + ); //Nastavujeme rodičovské atributy ); (koruna.getŠířka() - kmen.getŠířka()) / 2, koruna.getVýška() ); Výslednou podobu upravovaných tříd si můžete zkontrolovat podle vzorového řešení v projektu 08_Dědičnost_tříd_C. Obrázek 8.9 Projekt 08_Dědičnost_tříd_C Společný rodič Hýbací Možná vás už napadlo, že stejně, jako jsme udělali společného rodiče všech posuvných tříd, bychom mohli udělat i společného rodiče všech hýbacích tříd. Do definice této rodičovské třídy bychom pak podle předchozího vzoru přesunuly atributy šířka a výška a s nimi i příslušné přístupové metody. Máte pravdu, je to logické pokračování našeho úsilí. Proto jsem je pro vás nachystal jako další úlohu. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 362 z 433 Kapitola 8: Třídy mohou také dědit 363 Definujte třídu Hýbací, která bude společným rodičem všech tříd implementujících rozhraní IHýbací, tj. tříd Elipsa, Obdélník, Trojúhelník a Strom.. Výslednou podobu upravovaných tříd si můžete zkontrolovat podle vzorového řešení v projektu 08_Dědičnost_tříd_D. Obrázek 8.10 Projekt 08_Dědičnost_tříd_D 8.5 Abstraktní metody a třídy Už jsme svůj projekt hodně zestručnili, ale stále to ještě není dokonalé. Naše třídy dědí od svého společného rodiče leccos, ale nemohou do něj zdědit to, že implementují rozhraní IPosuvný, resp. IHýbací, protože součástí implementace je i implementace metody nakresli(Java.awt.Graphics2D), kterou naše rodičovské třídy prozatím úspěšně ignorují. Ono jim také nic jiného zatím nezbývá. Mohly by sice implementovat tuto metodu jako prázdnou nebo by při jejím spuštění mohly vyvolat nějaký chybový stav, ale to není optimální řešení. Takovýmto řešením bychom se totiž připravili o to, aby se případná chyba projevila již při překladu a zbytečně bychom její odhalení odsunuly až do doby běhu se všemi z toho plynoucími negativními důsledky. Potřebovali bychom nějakou konstrukci, která by nám dovolila oznámit, že dceřiné třídy implementují nějaké rozhraní a připravit jim k tomu vše potřebné s výjimkou metod, pro jejichž definici nemá rodičovská třída dostatek informací. Protože rodičovská třída neimplementuje všechny metody daného rozhraní, stává se ʺnedokonalouʺ a nemělo by být možné vytvořit její instance (jak by reagovaly, kdyby po nich někdo takovou metodu chtěl vyvolat?). Protože takováto situace není zase až tak výjimečná, zavádějí objektově orientované jazyky tzv. abstraktní metody, což jsou metody, které jsou (stejně jako to dělá rozhraní) pouze deklarovány, avšak nejsou implementovány. Třída, která obsahuje nějakou abstraktní metodu se pak označuje jako abstraktní třídou a od ostatních tříd se liší tím, že nám překladač nedovolí vytvořit její instanci. Potkáte-li proto v programu instanci abstraktní třídy, může to být (stejně jako u rozhra- @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 363 z 433 364 Myslíme objektově v jazyku Java 1.5 ní) jedině instance nějakého jejího potomka, který se za její instanci vydává (to potomci mohou). Jak jsme si právě řekli, abstraktní třída nemůže mít vlastní instance, to mohou mít až její potomci, a to navíc jen ti, kteří již mají všechny metody implementovány. Abychom tyto dva druhy tříd v textu odlišili, označujeme třídy, které mohou mít vlastní instance, jako konkrétní třídy. Obdobně je to i s metodami. Metody, které mají implementaci, označujeme někdy jako konkrétní metody. Abstraktní třída je vlastně takovým hybridem slučujícím vlastnosti konkrétní třídy a rozhraní. S konkrétní třídou má společné to, že její dceřiné třídy od ní mohou dědit implementované metody (tj. mohou dědit implementaci), s rozhraním má zase společné to, že může metody pouze deklarovat a jejich implementaci tak svým konkrétním potomkům vnutit, aniž by je musela sama implementovat. Abstraktní metody bychom mohli rozdělit na dva druhy: metody, které deklaruje některé z touto třídou implementovaných rozhraní, ale které třída neimplementuje, metody, které třída sama deklaruje, ale neimplementuje. Neimplementovaná metoda implementovaného rozhraní S prvním případem jsme se již potkali ve chvíli, kdy jsme zapomněli implementovat nějakou metodu rozhraní, k jehož implementaci se třída přihlásila. Tehdy to byla chyba, nyní to již může být záměr. Natáhněte v diagramu tříd implementační šipky od třídy Posuvný k rozhraní IPosuvný a od třídy Hýbací k rozhraní IHýbací. Nyní se pokuste obě třídy přeložit. Překladač se vzbouří a ohlásí chybu, o které jsem se před chvílí zmiňoval. Pomoc je jednoduchá: označte třídu jako abstraktní. Toho dosáhnete jednoduše: mezi modifikátory v hlavičce třídy přidáte klíčové slovo abstract: public abstract class Posunvý implements IHýbací public abstract class Hýbací extends Posuvný implements IHýbací @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 364 z 433 Kapitola 8: Třídy mohou také dědit 365 Jak si můžete vyzkoušet, překladač se uklidní a vaši třídu přeloží. Když se podíváte na diagram tříd, tak navíc zjistíte, že se v bloku dané třídy objevil stereotyp «abstract» příslušný blok se přebarví na růžovo. Současně ale třída přestane nabízet možnost zkonstruovat svůj objekt. To se ale dalo očekávat. Před chvílí jsme si přece říkali, že vzhledem k tomu, že neimplementuje všechny deklarované metody, bude třída považována za ʺnedokonalouʺ a nebude ji povoleno vytvářet instance. Abychom na první pohled odlišili abstraktní třídy od konkrétních, zavedeme si konvenci, podle které budeme před název abstraktních tříd přidávat předponu A. Přejmenujte proto naše nové abstraktní třídy na APosuvný a AHýbací. Definujte pak tyto abstraktní třídy jako třídy implementující příslušná rozhraní (tj. natáhněte implementační šipky) a ověřte, že překladač vše úspěšně přeloží. Odstraňte poté všechny implementační šipky směřující od dceřiných tříd k rozhraním a znovu projekt přeložte. I tentokrát by měl být překladač vše přeložit bez problémů. Obrázek 8.11 Projekt 08_Dědičnost_tříd_E Nově deklarovaná abstraktní metoda Třída může také sama deklarovat abstraktní metodu, kterou sice sama implementovat nemůže, ale všichni její konkrétní potomci by ji implementovat měli. Takovouto metodu deklarujete obdobně, jako byste ji deklarovali v rozhraní, jenom nesmíte zapomenout uvést správný modifikátor přístupu a mezi modifikátory musíte přidat klíčové slovo abstract – např.: public void veřejnáAbstraktníMetoda( String parametr ); protected int chráněnáAbstraktníMetoda( int parametr ); K abstraktním metodám bych ještě uvedl pár poznámek: Klíčové slovo abstract používáme proto, abychom překladači potvrdili, že středník se za hlavičkou neobjevil tak, že nám pouze upadla ruka na klávesnici, ale že je zde schválně. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 365 z 433 366 Myslíme objektově v jazyku Java 1.5 Abstraktní metody nově deklarované v abstraktní třídě musí mít uveden modifikátor přístupu. Na rozdíl od rozhraní to však nemusí být pouze modifikátor public, ale můžete použít i modifikátor protected. Deklarované abstraktní metody nemohou mít uveden modifikátor přístupu private, protože na ně potomci, kteří je mají překrýt, musí vidět. Připomínám, že abstraktní metoda se od konkrétní metody liší tím, že si její deklarací vynucujete na všech potomcích, aby tuto metodu implementovali. Konkrétní metodu mohou potomci zdědit a vůbec se o ní nemusí starat, abstraktní metodu musí implementovat stejně, jako musí implementovat všechny metody implementovaných rozhraní. Poslední tvrzení si hned ověříme. Představte si, že bychom chtěli definovat nějaký speciální typ přesouvače, jehož rychlost přesunu by závisela na tom, jak velký objekt přesouvá. Malé objekty by proto dokázal přesouvat rychleji než velké. Aby mohl program rozhodnout, jak rychle má přesouvač přesouvat, potřeboval by znát plochu přesouvaného objektu. Definujte proto v třídě APosuvný abstraktní metodu plocha(), která vrátí velikost plochy daného objektu. public abstract int plocha(); Zkusíte-li nyní kteroukoliv z jejích konkrétních dceřiných tříd přeložit, překladač se opět vzbouří, protože v jejich zdrojových souborech nenalezne požadovanou implementaci. Jakmile v některé třídě příslušnou metodu implementujete (její těl může klidně zůstat prázdné), překladač ji opět bez námitek přeloží. Abstraktní třída bez abstraktních metod Zatím jsme abstraktnost třídy vynucovali tím, že jsme v ní nebo v jí implementovaných rozhraních deklarovali metody, které pak třída neimplementovala. Třídu však můžeme deklarovat jako abstraktní i bez toho, že by měla jakékoliv resty v oblasti neimplementovaných metod. Jednou za čas se nám hodí definovat společnou rodičovskou třídu skupině tříd, avšak budeme nadále trvat na tom, aby se od této rodičovské třídy nevytvářely žádné instance. Pak stačí definovat danou třídu jako abstraktní nezávisle na tom, že žádné abstraktní metody neobsahuje. Překladač se k ní bude chovat stejně, jako k ostatním abstraktním třídám a nikomu nedovolí vytvořit její samostatnou instanci. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 366 z 433 Kapitola 8: Třídy mohou také dědit 8.6 367 Návrhový vzor Stav podruhé V podkapitole Návrhový vzor Stav (State) na straně 298 jsme se seznámili s návrhovým vzorem Stav. V tehdejší realizaci zastupoval stavově závislou část instancí multistavové třídy jejich speciální atribut deklarovaný jako instance stavového rozhraní. Toto rozhraní pak implementovalo několik jednostavových tříd, z nichž každá popisovala chování objektu v jednom konkrétním stavu. Při našich současných znalostech bychom mohli tehdejší architekturu vylepšit o to, že bychom stavové rozhraní nahradili třídou (budu jí říkat stavová třída), jež by implementovala části kódu, které jsou všem jednostavovým třídám společné, a tím by definice jednostavových tříd zjednodušila. Protože ve stavovém rozhraní budou určitě zbývat metody, které v této stavové třídě implementovány nebudou (kdyby v ní byly definovány všechny metody, nepotřebovali bychom dceřinné jednostavové třídy), bude tato stavová deklarována jako abstraktní. Projekt Šipka Abyste si pocvičili deklaraci abstraktních metod a definici společné rodičovské třídy, připravil jsem pro vás malé cvičení: Zkuste shrnout zkušenosti získané v předchozích dvou podkapitolách a upravte výše popsaným způsobem třídy řešící úlohu s otáčejícími se šipkami. Nahraďte rozhraní IŠipka abstraktní třídou AŠipka, která bude obsahovat implementací společných, stejně definovaných atributů a metod z jednostavových tříd VŠipka, SŠipka, ZŠipka a JŠipka. Řešení úlohy je tak jednoduché, že se skoro stydím předvádět vzorové řešení. Ukazuji je pouze proto, že vím, že abstraktní třídy jsou pro vás nové a možná si budete chtít potvrdit, že jste vše správně pochopili. (Novou podobu multistavové třídy Šipka nepředvádím, protože se v ní změní pouze typ stavového atributu.) public abstract class AŠipka { //== KONSTANTNÍ ATRIBUTY TŘÍDY ================================================= protected static final AktivníPlátno AP = AktivníPlátno.getPlátno(); //== PROMĚNNÉ ATRIBUTY TŘÍDY =================================================== //== KONSTANTNÍ ATRIBUTY INSTANCÍ ============================================== protected final Trojúhelník špička; @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 367 z 433 368 Myslíme objektově v jazyku Java 1.5 protected final Obdélník tělo; //############################################################################## //== KONSTRUKTORY A METODY VRACEJÍCÍ INSTANCE VLASTNÍ TŘÍDY ==================== //Prázdný implicitní konstruktor vyhovuje //== ABSTRAKTNÍ METODY ========================================================= public public public public public abstract abstract abstract abstract abstract int getX(); int getY(); void setPozice(int x, int y); AŠipka doleva(); void vpřed(); //== NOVĚ ZAVEDENÉ METODY INSTANCÍ ============================================= public void nakresli( Kreslítko g ) { špička.nakresli( g ); tělo .nakresli( g ); } }//public abstract class AŠipka Z jednostavových tříd uvádím opět pouze definici třídy VŠipka. public class VŠipka extends AŠipka { //############################################################################## //== KONSTRUKTORY A METODY VRACEJÍCÍ INSTANCE VLASTNÍ TŘÍDY ==================== public VŠipka( int x, int y, Barva barva ) { int krok = AP.getKrok(); int k2 = krok/2; špička = new Trojúhelník( x*krok + k2, y*krok, k2, krok, barva, Směr.V ); tělo = new Obdélník ( x*krok, y*krok + krok/4, k2, k2, barva ); } //== PŘÍSTUPOVÉ METODY ATRIBUTŮ INSTANCÍ ======================================= public int getX() { return tělo.getX(); } public int getY() { return špička.getY(); } public void setPozice(int x, int y) { int krok = AP.getKrok(); @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 368 z 433 Kapitola 8: Třídy mohou také dědit } 369 špička.setPozice( x + krok/2, y ); tělo .setPozice( x, y + krok/4 ); //== PŘEKRYTÉ ABSTRAKTNÍ METODY RODIČOVSKÉ TŘÍDY =============================== public AŠipka doleva() { int krok = AP.getKrok(); return new SŠipka( getX()/krok, getY()/krok, špička.getBarva() ); }//public IŠipka vlevoVbok() public void vpřed() { setPozice( getX()+AP.getKrok(), getY() ); }//public void vpřed() }//public class VŠipka Jak vidíte, moc se toho nezměnilo. Do rodičovské třídy se přesunuly pouze atributy a metoda nakresli(Kreslítko). Zajímavější situace by nastala ve chvíli, kdy bychom se rozhodli, že by bylo efektnější přesouvat šipka pomocí přesouvače. Pak by bylo rozumné, aby metodu vpřed() implementovala pouze metoda AŠipka s tím, že dceřiné třídy budou místo metody vpřed() implementovat metodu před(), která bude vracet pozici, na kterou by se měla daná šipka přesunout při pohybu o jedno pole vpřed. Třída AŠipka by pak příslušnou instanci přesunula do této cílové pozice pomocí přesouvače. 8.7 Co je na dědičnosti špatné Název podkapitoly vás možná zarazil. Celou dobu vám tu vykládám o dědičnosti tříd a popisuji její nejrůznější vlastnosti a najednou vložím podkapitolu, kde se nejspíš chystám dědičnost pomlouvat. Bohužel, je tomu tak. Největší slabinou dědičnosti tříd je to, že dědičnost narušuje to, co si na objektově orientovaném programování ceníme nejvíce, a to je zapouzdření. V úlohách, které jsme v této kapitole řešili, jsme se o tom mohli nejednou přesvědčit. V mnoha případech jsme mohli správně naprogramovat překrývající dceřinou metodu pouze tehdy, když jsme měli podrobné informace o tom, jak je naprogramována její rodičovská třída. Za všechny bych např. uvedl problémy s umísťováním našich xobdélníků v pasáži Metoda setPozice(int,int) na straně 346. Do obdobných problémů bychom se dostali, kdybychom se při definici třídy Terč rozhodli překrýt metodu setRozměr(int,int) a nevěděli (nebo na to zapo@Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 369 z 433 370 Myslíme objektově v jazyku Java 1.5 mněli), že rodičovská třída Kruh definuje její jednoparametrickou přetíženou verzi setRozměr(int) tak, že volá metodu setRozměr(int,int) s oběma parametry stejnými. Kdybychom pak upravovali velikost největšího kruhu (ten je rodičovským podobjektem terče) tak, že bychom zavolali metodu setRozměr(int), tak by se nám celý program zhroutil. Tato metoda by totiž zavolal onu dvouparametrickou, kterou ale právě překrýváme, takže by vlastně zavolal metodu, která volá ji, a už by se program vrtěl jako nudle v bandě. Jak jsme si ale před tím mnohokrát říkali, třída by měla být naprogramována tak, aby nikdo neznal způsob implementace jednotlivých metod. Bude-li s třídou a jejími instancemi někdo komunikovat pouze na základě zveřejněného rozhraní. Budete-li chtít poslední pravidlo dodržet, musíte do rozhraní (přesněji do kontraktu) zahrnout všechny informace o implementaci, které by měl případný tvůrce dceřiných tříd vědět. Jakmile tak učiníte, musíte počítat s tím, že rozhraní je nedotknutelné. Co se jednou objeví v rozhraní a příslušném kontraktu, musí tak už zůstat navždy (přesněji mělo by). Než proto svoji třídu zveřejníte a dáte jiným programátorům v plen, dobře si rozmyslete, jak bude její rozhraní vypadat. 8.8 Kdy (ne)použít dědičnost Při tvorbě tříd, jejichž instance mají vztah k instancím jiných tříd, používám dva mechanizmy: kompozici, neboli skládání, kdy instance souvisejících tříd vystupují jako atributy nově vytvořené třídy (takto byla konstruována např. třída Strom), dědičnost, při níž jsou instance nově vytvářené třídy speciálním případem instancí původní třídy (takto byly vytvářeny třídy Čtverec a Kruh). Mnozí začátečníci (a nejen začátečníci) jsou natolik uchváceni možnostmi dědičnosti tříd, že jí používají i tam, kde je to naprosto nevhodné a ke by bylo daleko výhodnější použít skládání. Autor jedné knihy o objektovém programování např. uváděl příklad žáka, který za ním přišel s dotazem, jak má naprogramovat třídu Letadlo, která je potomkem třídy Křídlo, když letedlo má křídla dvě. Již na počátku kapitoly jsem říkal, že řada programátorů si plete význam a účel dědičnosti. Neberou dceřinou třídu jako něco, co specifikuje podmnožinu instancí rodičovské třídy, ale jako něco, co od svého rodiče zdědí řadu výhodných atributů a metod a k těmto zděděným atributům a metodám rodičovské třídy přidá další. Dceřiné třídy opravdu často něco přidávají (vzpomeňte si na dvojici tříd Posuvný – Hýbací), ale často naopak možnosti svých rodičů omezují (typickým pří@Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 370 z 433 Kapitola 8: Třídy mohou také dědit 371 kladem jsou třídy Čtverec a Kruh). Nicméně přidávání atributů a/nebo metod nesmí být hlavním důvodem pro rozhodnutí o definici nové třídy jako potomka třídy stávající. Obecně bychom měli říci, že v prvním plánu byste měli uvažovat o skládání a k úvahám o dědičnosti přistoupit jet tehdy, když si o to vlastnosti tříd vysloveně „koledují“. Teď možná ještě budete často na pochybách, co zvolit. Věřím ale, že v průběhu zbytku učebnice budete mít dost příležitostí se setkat s oběma druhy kooperace tříd, abyste se naučili ve své budoucí praxi rozhodovat optimálně. Co jsme dělali špatně A hned začnu tím, že pomluvím to, co jsem vám před chvílí ukazoval. V pasáži Složitější dceřiná třída na straně 344 jsme konstruovali třídu XObdélník jako potomka třídy Obdélník a v následující úloze jsem vám zadal zkonstruovat třídu Terč jako potomka třídy Kruh. Obě tyto třídy jsme definovali jako dceřiné proto, abych vám na jejich konstrukci mohl ukázat různé záludnosti, které vás mohou při tvorbě dceřiných tříd potkat. Doufal jsem přitom, že vnější grafická podoba instancí ve vás vyvolá dojem, že definovat tyto třídy jako potomky doporučených rodičů je správné. A vidíte, není. Když nad tím budete chvíli přemýšlet, určitě dospějete k závěru, že xobdélník opravdu není speciálním případem obdélníku a terč speciálním případem kruhu. Oba totiž nabourávají jednu ze základních vlastností těchto tvarů, se kterou všechny třídy, které dané tvary používaly (např. náš Strom) počítaly. Touto vlastnosti byl referenční bod, vůči kterému se počítala pozice obrazce. U obdélníku i kruhu se jejich pozice počítala jako pozice levého horního rohu opsaného obdélníku, kdežto naše dceřiné třídy definovaly jako vztažný bod střed své grafické reprezentace. Kdybychom proto použili terč a xobdélník při konstrukci stromu, vznikl by útvar, který by byl daleko abstraktnější, než byl náš původní záměr. Obě třídy, tj. Xobdélník i Terč, definovaly rozšíření svých rodičů obdobně, jako výše pomlouvaný obdélník a úsečka definovaly rozšíření bodu. Kdybych zapomněl na výukové důvody a díval se na úlohu jenom programátorsky, tak bych s definicí těchto tříd počkal, až bude definována třída Hýbací, resp. AHýbací, definoval obě třídy jako jejího potomka a potřebný obdélník, resp. vnější kruh definoval jako atributy. (Zkuste si to a uvidíte, jak se leccos zjednoduší.) Oba příklady jsou tu uvedeny jako názorný příklad toho, jak svůdné může být někdy použití dědičnosti a do jakých problémů se dostanete, pokud těmto svodům podlehnete. Otestujete-li třídy XObdélník a Kruh nyní po zavedení tříd @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 371 z 433 372 Myslíme objektově v jazyku Java 1.5 APosuvný a AHýbací, zjistíte, že zase nechodí. Zkuste je rozchodit a uvidíte, že to nebude úplně triviální. Definujte třídy XobdélníkP a TerčP jako potomky třídy AHýbací a porovnejte jejich definice s definicemi jejich předchůdců. Kdy dát přednost skládání a kdy dědičnosti Dědičnost jsme tedy pomluvili, ale v řadě případů je to stále nejlepší možné řešení. Povězme si proto pár záchytných bodů, podle kterých byste se mohli rozhodovat, zda v daném případě dát přednost spíše skládání objektů, anebo zda pro vás bude výhodnější využití dědičnosti. Mějte na paměti, že ve většině případů je daleko výhodnější (a často jediné rozumné) použití skládání. O využití skládání byste proto měli přemýšlet nejdříve. Rozhodnete-li se použít dědičnost, měli byste pro to mít pádné důvody. První otázkou, kterou byste si měli položit je: Budu potřebovat přetypovat instanci na předka? Odpovíte-li si na ni ANO, bude nejlepším řešením využití dědičnosti. Odpovíte-li si NE, budete se ptát dál. Další otázku, kterou byste si měli položit je: Je definovaná třída speciálním případem své rodičovské třídy? Jinými slovy: jsou instance vytvářené třídy podmnožinou instancí rodičovské třídy? Pokud ANO, použijte dědičnost. Pokud ne, bude nejspíše výhodnější použít skládání. 8.9 Shrnutí – co jsme se naučili Pro podmnožinu instancí nějaké třídy majících speciální vlastnosti můžeme zavést vlastní třídu. Tuto třídu označujeme jako dceřinou, odvozenou nebo jako podtřídu. Jejím protějškem je rodičovská třída označovaná také jako bázová či základní třída a nebo jako nadtřída. V jazyku Java má každá třída právě jednoho rodiče (ani víc, ani méně). Jedinou výjimkou je systémová třída Object, která je společným (pra)rodičem všech ostatních tříd a sama již žádného rodiče nemá. Dceřiná třída dědí od své rodičovské třídy všechny atributy a metody, a to i soukromé, i když k těm nemá přístup. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 372 z 433 Kapitola 8: Třídy mohou také dědit 373 Každá instance dceřiné třídy v sobě obsahuje podobjekt své rodičovské třídy. Při konstrukci instance se nejprve zavolá konstruktor jejího rodičovského podobjektu. Konstruktor lze zavolat zapsáním klíčového slova super následovaného seznamem parametrů v závorkách. Není-li tento příkaz použit, pokusí se překladač zavolat bezparametrický rodičovský konstruktor. Volání rodičovského konstruktoru musí být naprosto prvním příkazem těla konstruktoru. Není-li volaný rodičovský konstruktor dostupný (neexistuje nebo je private), ohlásí překladač chybu. V jednom konstruktoru není možné volat současně super a this. Při vytváření konstruktorů dceřiné třídy je vhodné nezapomenout na konstruktor, který využívá všech možností konstruktoru rodičovské třídy, protože jinak bude tento rodičovský konstruktor pro potomky v další generaci nedostupný. Vedle modifikátorů přístupu private a public existuje i modifikátor protected. Jím označené atributy a metody jsou přístupné ostatním třídám v daném projektu a v jiných projektech pouze potomkům dané třídy. Protože atributy a metody třídy kvalifikujeme názvem třídy, budou statické složky rodičovské třídy dostupné i v případě, že dceřiná třída definuje stejně pojmenované statické složky. Dceřiná třída může ke zděděným atributům a metodám přidat atributy a metody vlastní. Definuje-li instance stejnou metodu jako je metoda zděděné od rodičovské třídy, zakryje tato nová metoda metodu zděděnou. Překrývající metoda bude použita i v případech, kdy je v definici rodičovské třídy původně použita metoda překrytá. Překrytou verzi metody lze vyvolat prostřednictví klíčového slova super. V definicích metod je třeba pamatovat na to, že dceřiné třídy mohou metodu překrýt a umožnit jim tohoto překrytí využít. V prostředí BlueJ lze vztah dědičnosti tříd zadat natažením dědické šipky a zrušit odstraněním této šipky. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 373 z 433 374 Myslíme objektově v jazyku Java 1.5 Máme-li několik tříd s velmi podobnými vlastnostmi, může být výhodné pro ně definovat společnou rodičovskou třídu, do které se přesunou společné atributy a metody. Třídu můžeme deklarovat jako abstraktní. Abstraktní třída nemůže vytvářet vlastní samostatné instance. Její instance mohou být pouze podobjekty jejích dceřiných tříd. Třídu, která není abstraktní, označujeme jako konkrétní. Abstraktní třída nemusí implementovat všechny metody implementovaných rozhraní. Abstraktní třída může deklarovat vlastní abstraktní metody, které pouze deklaruje, ale neimplementuje. Jejich implementaci přenechává svým konkrétním potomkům. Abstraktní třída je slučuje vlastnosti třídy a rozhraní. Její potomci od ní mohou dědit jak implementaci, tak povinnost něco implementovat sami. Při konstrukci nových tříd jejichž instance mají nějaký vztah k současným třídám se musíme rozhodnout mezi použitím skládání a dědičnosti. Ve většině případů je výhodnější použití skládání než použití dědičnosti. Dědičnost bychom měli použít opravdu jen tehdy, budou-li instance dceřiné třídy speciálními případy instancí rodičovské třídy. Pádným argumentem pro použití dědičnosti je očekávaná potřeba přetypovat instanci na předka. Při rozhodování o použití dědičnosti bychom neměli podlehnout svodům vnější podobnosti instancí. Nově zavedené termíny abstraktní metoda abstraktní třída bázová třída dceřiná třída chráněný přístup nadtřída odvozená třída podobjekt podtřída @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 374 z 433 Kapitola 8: Třídy mohou také dědit 375 prarodič protected překrytá metoda překrytí metod překrývající metoda rodič rodičovská třída rozšíření třídy základní třída @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 375 z 433 376 Myslíme objektově v jazyku Java 1.5 9. Budete si to přát zabalit? Kapitola 9 Budete si to přát zabalit? ☯ 9.1 Co se v kapitole naučíme V této kapitole. Velké programy a jejich problémy Jakmile začne složitost programu narůstat a tříd začnou být desítky, stovky a tisíce1, přestává být únosné udržovat všechny třídy po kupě, protože by se v nich brzy nikdo nevyznal. Je to obdobné, jako když máte všechny papíry na jedné hromadě na stole nebo když máte všechny soubory uložené v jedné složce. Zkušenost ukázala, že doba potřebná k vytvoření programu, roste exponenciálně s jeho velikostí. Budete-li schopni vytvořit v náhlém záchvatu geniality za jeden den tisíciřádkový program, vůbec z toho nevyplývá, že byste byli schopni vytvořit za týden 5000řádkový program a za měsíc 20000řádkový. Lze spíše očekávat, že k vytvoření 5000řádkového programu budete potřebovat skoro měsíc a 20000řádkový program nevytvoříte o moc rychleji než za rok. Jedním z cílů správného návrhu je rozdělit program na několik menších částí, které spolu budou pouze minimálně souviset. Každou část je pak možno vyvíjet relativně samostatně a ve vhodných intervalech pouze přezkušovat správnou spolupráci jednotlivých částí. Jednou takovou částí je třída. Je to však příliš malá část (i když může mít i několik tisíc řádků) a při tvorbě opravdu velkých programů s takovýmto dělením 1 Samotná standardní knihovna Javy měla v době psaní knihy 12 798 (slovy dvanáct tisíc sedm set devadesát osm) tříd. Z nich je 3270 tříd veřejných a začleněných do standardní dokumentace. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 376 z 433 Kapitola 9: Budete si to přát zabalit? 377 nevystačíme (jak brzy poznáte, nevystačíme s ním dost dobře ani u programů menších). Potřebujeme mít možnost vytvářet části vyššího řádu. Při správném rozdělení programu můžeme s výhodou využít i to, že uvnitř části si mohou třídy navzájem před sebou odhalit část roušky zapouzdření, aby pak tyto znalosti mohly využít ke zvýšení efektivity svojí práce. Přepokládá se přitom, že všechny třídy uvnitř dané části jsou dílem jednoho nebo několika málo programátorů, kteří jejich kód důvěrně znají a riziko nekorektní modifikace kódu je u nich proto výrazně menší. 9.2 Balíčky Java umožňuje sdružovat skupiny tříd do hierarchicky uspořádaných balíčků (package). Při ukládání zdrojových souborů na disk je přitom dobrým zvykem (a většina vývojových prostředí na tom dokonce trvá), že zdrojové soubory tříd patřících do společného balíčku se ukládají do stejné složky. Překladač potom stejným způsobem uspořádá i přeložené soubory. Zdrojové a přeložené soubory mohou, ale nemusí být ve stejné složce. BlueJ umísťuje přeložené soubory (soubory s příponou class) do stejné složky jako zdrojové soubory, ale většina profesionálních prostředí dává přednost oddělenému umístění zdrojových, přeložených a testovacích souborů. K tomu, aby překladač uznal třídu jako součást balíčku, nestačí pouhé umístění zdrojového textu do příslušné složky. Programátor ještě musí explicitně potvrdit, že soubor sem nezabloudil omylem, ale že sem opravdu patří. Proto je potřeba, aby zdrojový text začínal příkazem: package nazev_balíčku; kde nazev_balíčku musí být stejný jako název složky, v níž je soubor umístěn. Protože název balíčku je identifikátorem, logicky z toho vyplývá, že názvy složek s balíčky musí dodržovat pravidla pro tvorbu identifikátorů, tj. smějí obsahovat pouze písmena, číslice a znaky „_“ a „$“, nesmějí začínat číslicí a nesmějí být shodné s žádným klíčovým slovem. Podle konvence by měl navíc název balíčku obsahovat pouze malá písmena (je to sice jenom konvence k jejímuž dodržování vás překladač nenutí, ale je všeobecně dodržovaná). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 377 z 433 378 Myslíme objektově v jazyku Java 1.5 I u názvů balíčků platí, že Java rozlišuje velikost znaků. Pracujete-li ve Windows, tak víte, že tento operační systém velikost znaků v názvech souborů a složek sice zobrazí, ale jinak ji nebere na vědomí. Složka serial a SERIAL je pro něj jedna a ta samá složka. Ne tak pro Javu. V příkazu import proto musíte použít přesně stejnou velikost znaků, jakou mají názvy příslušných souborů a složek. Tady musím opět upozornit na problémy s češtinou. Některá prostředí totiž mají problémy s převodem kódů a názvy souborů a složek nejsou schopna správně rozpoznat. Jak jsem již řekl, příkaz package musí být prvním příkazem v souboru, před ním smějí předcházet pouze mezery a komentáře. Třída může být navíc pouze v jediném balíčku (jeden a tentýž zdrojový soubor nemůže být zároveň ve dvou složkách) – proto také může být na začátku třídy pouze jediný příkaz package. Podbalíčky Stejně jako mohou složky obsahovat podsložky, mohou balíčky obsahovat podbalíčky. Název podbalíčku se skládá z názvu rodičovského balíčku následovaného tečkou a názvem podbalíčku. Podbalíčky mohou mít opět podbalíčky, a to do libovolné hloubky. Pro název platí stále stejná konvence. Balíčky tak mohou vytvářet stejnou stromovou strukturu jako složky a i pro jejich názvy platí obdobná pravidla, jako pro absolutní cestu k dané složce – pouze se lomítka nebo zpětná lomítka (záleží na operačním systému) nahradí tečkami. Balíčky a podbalíčky jsem používal i při přípravě doprovodných programů a příkladu k prvním osmi kapitolám této učebnice. Pak jsem vám sice potřebné programy sesypal do jednoho projektu, takže vy jste měli všechny třídy pěkně na hromádce, ale chtěl-li jsem se v jednotlivých etapách vývoje programů trochu vyznat, pro sebe jsem si je do balíčků rozmístit musel. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 378 z 433 Kapitola 9: Budete si to přát zabalit? 379 Uspořádání podbalíčků s programy k dosavadní části knihy Podívejte se např. na to, jsou doprovodné programy a řešené úlohy k dosavadním kapitolám uspořádány Všechny zdrojové soubory umisťuji do balíčku-složky rup. V tomto balíčku jsou podbalíčky (podsložky) určené jednotlivým jazykům, do nichž se učebnice překládá. Nás bude nyní zajímat podbalíček nazvaný česky, tj. balíček rup.česky. V balíčku rup.česky jsou podbalíčky další úrovně věnované jednotlivým kapitolám – např. třetí kapitole je věnován podbalíček rup.česky._02_třídy. Uvnitř balíčku věnovaného kapitole jsou další podbalíčky věnované jednotlivým probíraným oblastem. V balíčcích kapitol první a druhé části většinou naleznete podbalíček (= podsložku) tvary, v němž jsou umístěny třídy přímo související a naším kreslícím plátnem. Pokud se v průběhu kapitoly podoba tříd v balíčku změní, může zde být takovýchto balíčků i několik. V každé kapitole nalezete i podbalíček příklady, v němž jsou umístěny jednodušší příklady dané kapitoly. Jsou-li součástí kapitoly větší příklady sestávající z několika tříd, mívají většinou svůj vlastní balíček. Obrázek 9.1 Stromová struktura balíčků počátečních kapitol projektu Za balíčky jednotlivých kapitol následuje balíček rup.česky.společně a balíček rup.česky.tvary. Do takovýchto samostatných balíčků umisťuji třídy v okamžiku, kdy jsme je již vypiplali do více méně konečné podoby, ve které mají šanci nějakou dobu zůstat. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 379 z 433 380 Myslíme objektově v jazyku Java 1.5 Obdobných zásad se budu držet i při umísťování programů pro tuto a následující kapitoly. Jediným rozdílem bude, že od této chvíle vám již nebudu setřásat třídy na jednu hromádku a vytvářet nový projekt bez balíčků, ale ponechám třídy v původních balíčcích. Žáci se mne občas ptají, kolik tříd by mělo být v jednom balíčku. Tento počet závisí na tom, jak složitou úlohu třídy z daného balíčku řeší. Ve standardní knihovně je 10 balíčků, které obsahují jedinou třídu a 12 balíčků se dvěma třídami. Vedle nich je tu ale také řada balíčků s několika desítkami tříd. 9.3 Balíčky a BlueJ Asi se budete ptát, jak počítač pozná, kde má strom balíčků a podbalíčků svůj kořen. Odpověď je jednoduchá: sám to nepozná, musíme mu to nějak sdělit. Každé vývojové prostředí definuje svůj způsob, jak mu oznámíme, ve které složce se tento kořen nachází. BlueJ zachází s balíčky oproti jiným vývojovým prostředím poněkud nestandardně: každý balíček je po něj projektem (a jako takový musí obsahovat soubor bluej.pkg). Kořenovou složkou stromu balíčků je pak ta složka, jejíž rodičovská složka již neobsahuje soubor bluej.pkg a pro BlueJ proto není projektem. Nyní si již můžete sami odvodit, že název souboru bluej.pkg vznikl ze sousloví BlueJ package – v BlueJ totiž můžeme do jisté míry považovat balíček za projekt a projekt za balíček nebo přesněji strom balíčků. Balíčky můžeme (stejně, jako většinu jiných věcí) připravovat „ručně“ za pomoci nějakého správce souborů, nebo automaticky pomocí prostředků nabízených prostředím BlueJ. Příprava stromu balíčků pro BlueJ ve správci souborů Ruční příprava stromu balíčků je poměrně jednoduchá: 1. Rozmyslete si, jak bude vypadat struktura balíčků vašeho projektu. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 380 z 433 Kapitola 9: Budete si to přát zabalit? 381 2. Ve svém oblíbeném správci souborů vytvořte kořenovou složku budoucího stromu balíčků. Přitom zkontrolujte, že složka, v níž tuto kořenovou složku vytváříte, neobsahuje soubor bluej.pkg. 3. V čerstvě vytvořené kořenové složce vytvořte strukturu složek odpovídající požadované struktuře balíčků. 4. V kořenové složce vytvořte prázdný soubor bluej.pkg nejlépe např. textovým editorem). 5. Tento soubor zkopírujte do všech jejích podsložek odpovídajících balíčkům. 9.4 Hrajeme si s balíčky Vyzkoušíme si vše hned na nějakém jednoduchém příkladu a vytvoříme jednoduchý strom balíčků podle obr. 9.2. Postupujte podle následujícího návodu: 1. Otevřete složku, do které umísťujete svoje projekty. 2. Vytvořte v ní složku Kořen, která bude kořenovou složkou vytvářeného stromu balíčků. Obrázek 9.2 Cvičný strom balíčků 3. Ve složce Kořen vytvořte prázdný textový soubor pojmenovaný bluej.pkg. Pracujete-li ve Windows. můžete např. klepnout pravým tlačítkem myši v prázdné oblasti a v místní nabídce zadat příkaz Nový a v následně rozbalené podnabídce pak příkaz Textový dokument (viz obr. 9.3). Windows na vás sice vybafnou s dialogovým oknem, v němž budou tvrdit, že změníte-li příponu souboru, může se stát, že soubor nebude možno použít, ale tím se nenechte zmást a své zadání potvrďte. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 381 z 433 382 Myslíme objektově v jazyku Java 1.5 Obrázek 9.3 Vložení prázdného textového dokumentu 4. Ve složce Kořen vytvořte podložku kap_1 (budeme se tvářit, že připravujeme balíček pro první kapitolu knihy). 5. Do nově vytvořené podsložky zkopírujte soubor bluej.pkg. 6. Otevřete složku Kap_1 a vytvořte v ni podsložky příklady a tvary. 7. Do složky tvary zkopírujte z projektu 02_Objekty soubory s příponami .java a .pkg. Tím jste s přípravami hotovi a můžete začít zkoušet. Spusťte BlueJ a příkazem Projekt → Otevřít projekt… jej požádejte, aby otevřel projekt. V následně otevřeném dialogovém okně mu pak zadejte právě vytvořený projekt Kořen. BlueJ vás asi překvapí tím, že neotevře okno projektu Kořen, ale správně odhadne, že kořenový balíček je nezajímavý (není v něm žádní třída a obsahuje pouze jediný podbalíček) a že obdobně nezajímavý je i jeho podbalíček kap_1 a otevře proto rovnou okno podbalíčku tvary. ve kterém již najde nějaké třídy. Název balíčku, jehož okno je právě otevřeno, pak pro snazší orientaci uvede v záhlaví okna v hranatých závorkách za názvem projektu – viz obr. 9.4. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 382 z 433 Kapitola 9: Budete si to přát zabalit? 383 Obrázek 9.4 Diagram tříd balíčku kap_1.tvary Rodičovský balíček v diagramu tříd V okně projektu zobrazí BlueJ známé ikony z našeho prvního projektu (aby ne, když jsme sem příslušné soubory zkopírovali). Kromě nich ale pod ikonou třídy Směr prosvítá ikona připomínající ikonu používanou ve správci souborů pro označení složky. To je ikona balíčku a v tomto diagramu zastupuje rodičovský balíček. Odsuňte ikonu třídy Směr doprava a uvidíte, že na ikoně balíčku je text <go up>, symbolizující, že se jedná o ikonu zastupující rodičovský balíček aktuálního podbalíčku. Ikonu rodičovského balíčku není možné nikam přesunout, takže se jí musí zbytek diagramu tříd přizpůsobit. Poklepáním na ikonu balíčku otevřete nové okno s diagramem tříd tohoto balíčku. Ten obsahuje vedle ikony doprovodného textového dokumentu pouze dvě ikonu: ikonu balíčku tvary a ikonu rodičovského balíčku. Pokud otevřete i ten, zjistíte, že obsahuje pouze ikonu balíčku kap_1. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 383 z 433 384 Myslíme objektově v jazyku Java 1.5 Převedení obyčejné složky na balíček v BlueJ Jistě si vzpomenete, že jsme ve složce kap_1 vytvořili vedle složky tvary také složku příklady. Tu nám však BlueJ v diagramu tříd neukázal. Není to proto, že by v ní nebyly žádné zdrojové soubory. Je to proto, že v této složce není soubor bluej-pkg a BlueJ ji proto nepovažuje za balíček. Abychom to dokázali, zavřete BlueJ přesuňte soubor Směr.java ze složky tvary do složky příklady. Pak BlueJ znovu otevřete – uvidíte, že se nic nezměnilo s výjimkou toho, že přesunutá třída bude v diagramu projektu kap_1.tvary chybět. Zkusíme to napravit. Zavřete znovu BlueJ a zkopírujte do složky příklady soubor bluej.pkg. Je jedno, odkud to bude, protože si jej BlueJ beztak upraví. Důležité je, že tam nějaký bude. Zkuste nyní znovu otevřít v BlueJ projekt Kořen. Tentokrát již BlueJ nedoběhne až do balíčku kap_1.tvary, ale zastaví se hned v balíčku kap_1, protože zde má najednou dva podbalíčky a není tedy zcela jasné, kudy by se měl dál vydat Poklepáním na ikonu otevřete balíček příklady a měli byste v něm najít ikonu třídy Směr, kterou jsme sem před chvílí přesunuli. Vytvoření nového balíčku v BlueJ Nové balíčky nemusíme vytvářet pouze ve správcích souborů. Můžeme je vytvořit i přímo v BlueJ: 1. Zadejte příkaz Úpravy → Nový balíček. 2. BlueJ otevře dialogové okno, v němž se vás zeptá na název vytvářeného balíčku. Zadejte např. zvláštní. 3. V diagramu tříd se objeví nová ikona balíčku pojmenovaná zvláštní. Podíváte-li se ve správci souborů do složky příklady, zjistíte, že v ní BlueJ vytvořil podsložku zvláštní a otevřete-li ji, naleznete v ní prázdný soubor bluej.pkg. Putování stromem balíčků Klepněte v diagramu tříd pravým tlačítkem na ikonu balíčku zvláštní a otevřete její místní nabídku. Naleznete v ní dva příkazy: Otevřít a Odstranit. Zadejte příkaz Otevřít a otevřete tak okno s diagramem tříd daného balíčku. Budou v něm pouze dvě ikony: ikona doprovodného textového soubor a ikona rodičovského balíčku. Klepněte pravým tlačítkem na ikonu rodičovského balíčku a rozbalte tak její místní nabídku. Jak vidíte (viz obr. 9.5), místní nabídka rodičovského balíčku se od nabídky ʺnerodičovskéhoʺ balíčku liší: @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 384 z 433 Kapitola 9: Budete si to přát zabalit? 385 Neobsahuje příkaz Odstranit. Místo položky Otevřít obsahuje celou sadu příkazů umožňující otevřít kterýkoliv z (pra)rodičovských balíčků včetně kořenového nepojmenovaného balíčku. Obrázek 9.5 Místní nabídka rodičovského balíčku umožňuje otevřít kterýkoliv z (pra)rodičovských balíčků Zkuste si otevřít jednotlivé balíčky uvedené v místní nabídce a ověřte si, že při žádosti o otevření balíčku, jehož okno je již otevřeno, neotevře BlueJ nové okno, ale pouze aktivuje okno otevřené. Automatická redefinice příkazu package Na počátku kapitoly o balíčcích jsme si říkali, že prvním příkazem zdrojového kódu třídy, která není v kořenovém, nepojmenovaném balíčku, musí být příkaz package. O to se při použití BlueJ naštěstí nemusíte starat, protože BlueJ při otevírání okna daného balíčku zkontroluje příkazy package všech tříd v balíčku, a pokud v některé třídě podoba příkazu neodpovídá, tak jej ihned opraví. Otevřete si zdrojový kód kterékoliv z tříd, které jsme zkopírovali do balíčku kap_1.tvary a přesvědčte se, že začíná příkazem package kap_1.tvary; Pak můžete nahlédnout do balíčku kap_1.příklady, do kterého jsme přesunuli třídu Směr a přesvědčit se, že její zdrojový kód začíná správně příkazem package kap_1.příklady; @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 385 z 433 386 9.5 Myslíme objektově v jazyku Java 1.5 Jmenné prostory a příkaz import Vraťte se nyní do balíčku kap_1.tvary a požádejte o překlad jeho tříd. BlueJ přeloží všechny třídy s výjimkou třídy Trojúhelník, u které prohlásí cannot resolve symbol – class Směr a zvýrazní přitom řádek, na kterém je definovaná implicitní směr. Překladač je totiž ochoten použít v definici pouze třídy, které jsou ve stejném balíčku a budete-li chtít použít třídu z jiného balíčku, musíte mu to výslovně povědět a nebo použít plný název dané třídy, který sestává z názvu balíčku následovaného tečkou a názvem třídy. Naše třída Směr se po svém přestěhování jmenuje kap_1.příklady.Směr. Zkuste proto její název opravit a upravte deklaraci konstanty do tvaru: public static final kap_1.příklady.Směr IMPLICITNÍ_SMĚR = kap_1.příklady.Směr.SEVER; Zkuste nyní třídu znovu přeložit. Překladač tentokrát deklaraci konstanty akceptuje, ale zarazí se o kousek dál na řádku, na němž je deklarován atribut směr. Mohli bychom sice upravit definici názvu třídy i zde, ale to by nestačilo, protože se zdrojový kódu odkazuje na tuto třídu vícekrát. Použití plného názvu třídy je vhodné opravdu pouze tehdy, vyskytuje-li se třída ve zdrojovém kódu jednou či dvakrát. Jakmile se v programu vyskytuje častěji, je vhodnější jiný postup. Při něm překladači na začátku zdrojového souboru řekneme, že budeme chtít danou třídu používat, a v dalším programu pak již můžeme používat její stručný název bez nutnosti uvedení názvu balíčku. K deklaraci toho, že se chystáme používat třídu z jiného balíčku, slouží příkaz import, v němž je toto klíčové slovo následováno plným názvem třídy. V našem příkladu bychom tedy vložili za příkaz package příkaz: import kap_1.příklady.Směr; Tento příkaz musí být ale na počátku zdrojového souboru. Před ním smí být pouze příkaz package. Vložte tento příkaz import na počátek zdrojového souboru, odstraňte z deklarace konstanty IMPLICITNÍ_SMĚR plný název třídy (to sice není nutné, ale kód pak lépe vypadá) a požádejte znovu o překlad. Tentokrát již bude překladač spokojen a třídu přeloží. Import více tříd Zkusíme nyní něco maličko složitějšího. Otevřete balíček kap_1.příklady a příkazem Úpravy → Nová třída ze souboru do něj přidejte první verzi třídy Strom z třetí kapitoly, tj. třídu Strom_3a z projektu 03_Třídy_Z. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 386 z 433 Kapitola 9: Budete si to přát zabalit? 387 Pokusíte-li se nyní tuto třídu přeložit, překladač se podle očekávání vzbouří, protože nebude znát třídu Elipsa. Protože dopředu víme, že nebude znát ani třídy Barva, Obdélník a Plátno, potřebovali bychom deklarovat import každé z nich. V jednom příkazu import můžeme deklarovat pouze jednu třídu. Budeme-li ctít importovat několik tříd, musíme použít několik příkazů import. V našem případě by tedy mohl mít počátek třídy tvar: 1 2 3 4 5 6 7 8 9 package kap_1.příklady; import import import import kap_1.tvary.Barva; kap_1.tvary.Elipsa; kap_1.tvary.Obdélník; kap_1.tvary.Plátno; //============================================================================== //Zbytek zdrojového souboru Zkuste nyní třídu přeložit a pokud jste neudělali žádnou chybu, měl by překlad proběhnout bez chyby. Situace, kdy potřebujeme dovézt větší množství tříd z jednoho balíčku je poměrně častá. Proto Java nabízí zkrácenou podobu zápisu, při níž místo názvu importované třídy uvedeme hvězdičku. Tím překladači oznamujeme, že z daného balíčku dovážíme všechny třídy. V našem případě bychom tedy mohli spojit čtyři použité příkazy import do jediného příkazu: import kap_1.tvary.*; Hvězdičková verze příkazu import je sice stručná, ale není příliš doporučovaná, protože z ní není na první pohled poznat, které ze tříd importujete a které ne. Používejte ji proto opravdu pouze tehdy, budete-li z daného balíčku importovat převážnou většinu jeho tříd. Podbalíčky Při práci s balíčky musíte mít neustále na paměti, že podbalíček není součástí balíčku. Budu-li proto někdy tvrdit, že cosi platí pro všechny třídy v balíčku, bude to platit opravdu jenom pro ně a nesmíte si dané tvrzení přenášet na třídy v jejich podbalíčcích. Importujete-li proto pomocí hvězdičkové konvence všechny názvy tříd z daného balíčku, neimportujete tím ještě názvy tříd z jeho podbalíčků. Budete-li je chtít používat, musíte je importovat zvlášť. Znovu ale opakuji, že používání @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 387 z 433 388 Myslíme objektově v jazyku Java 1.5 hvězdičkového tvaru se považuje za méně čisté a některá prostředí dokonce vydávají v takovém případě varovné zprávy. Balíček java.lang Zvláštní postavení mezi všemi balíčky má balíček java.lang. V tomto balíčku jsou základní třídy, které jsou používané ve všech programech. Proto je tento balíček dovezen vždy implicitně, takže se již nemusí explicitně dovážet prostřednictvím příkazu import. My jsme prozatím veřejně používali z tohoto balíčku jedinou třídu, a to třídu java.lang.String. Brzy však dojde i na další. Nevýhody koncepce balíčků v BlueJ Používání balíčků je zejména u složitějších nutné, protože je výrazně zpřehlední a sníží tak pravděpodobnost chyb. Pro uživatele BlueJ však má jednu nevýhodu: přijdou o možnost definovat implementaci rozhraní třídou prostým natažením šipky. BlueJ totiž neumí natáhnout šipku mezi dvěma projekty a tím ani mezi dvěma balíčky. Budeme-li proto potřebovat dědit třídu nebo implementovat rozhraní z jiného balíčku, nezbude nám, než napsat příslušnou klauzuli do hlavičky třídy ručně. Stejně, jako v BlueJ přicházíme o možnost jednoduché definice dědičnosti tříd či implementace rozhraní, přicházíme někdy i o možnost jednoduchého získávání odkazů na instance. BlueJ vám totiž neumožňuje definovat třídu v jednom balíčku (projektu), zavolat její konstruktor nebo metodu, která vrací nějaký odkaz, a takto získaný odkaz umístit do zásobníku odkazů jiného balíčku (projektu) Přemosťovací třída Zdá-li se vám, že budete při práci v některém balíčku budete častěji potřebovat volat konstruktory a metody tříd z jiných balíčků přímo z BlueJ, mohlo by po vás být výhodné vytvořit přemosťovací třídu, ve které definujete metody třídy, které budou potřebné konstruktory a statické metody tříd z jiných balíčků volat za vás. Abych vám tuto ideu trochu naznačil, ukážu vám kód takové jednoduché přemosťovací třídy, kterou si můžete vytvořit a vyzkoušet v balíčku kap_1.příklady: package kap_1.příklady; @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 388 z 433 Kapitola 9: Budete si to přát zabalit? 389 import kap_1.tvary.AktivníPlátno; import kap_1.tvary.Barva; public class Most { public static Plátno getPlátno() { return Plátno.getPlátno(); } } public static Barva getBarva( String název ) { return Barva.getBarva( název ); } Přemosťovací třída se hodí tehdy, chcete-li s třídami a jejich instancemi ručně experimentovat a potřebujete-li k tomu přístup ke konstruktorům a/nebo metodám v jiných balíčcích. Nehodláte-li si však hrát s programem přímo v prostředí BlueJ, ale chcete-li jej např. pouze otestovat, žádnou přemosťovací třídu nepotřebujete, protože veškerý potřebný kód pro volání metod z jiných balíčků můžete přímo zapsat do metod testovací třídy. 9.6 Přístupová práva v rámci balíčku Výhodou umístění všech souvisejících tříd do jednoho balíčku není pouze přehlednost programu. Třídy, které jsou spolu uvnitř jednoho balíčku mají totiž vůči sobě větší práva, než třídy, které jsou každá v jiném balíčku. Můžeme to chápat tak, že třídy v balíčku spolu kamarádí a proto si vzájemně i více důvěřují. Jsou proto ochotny umožnit svým „kamarádům“ přístup k některým složkám (atributům a metodám), k nimž třídy z jiných balíčků nepustí. Složky, k nimž je třída ochotna pustit své „kamarády“ nemají žádný modifikátor přístupu. Tento implicitní modifikátor přístupu je tedy vedle modifikátorů public, protected a private čtvrtou verzí specifikace přístupových práv (víc jich Java nemá). V hierarchii přísnosti vystupuje jako druhý nejpřísnější. Pro implicitní přístupová práva se používá bud výstižnější termín package private (soukromé v balíčku) a nebo méně výstižný (avšak v textu lépe použitelný) termín friendly – přátelské (přátelům toho dovolíme víc než ostatním). @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 389 z 433 390 Myslíme objektově v jazyku Java 1.5 Zopakujme si nyní celou hierarchii přístupových práv: private povoluje přístup pouze kódu dané třídy, package private jako oprivate, ale nevíc povoluje přístup i kódu tříd ve stejném balíčku, protected jako package private, ale navíc povoluje přístup i kódu dceřiných tříd, byť jsou z jiných balíčků, public povoluje přístup úplně každému. Použití přátelských složek usnadňuje tvorbu programů. Umožňuje totiž třídám sdílet v rámci balíčku některé pomocné metody a proměnné, o kterých nemusí ostatní třídy vůbec vědět. Této možnosti byste však neměli zneužívat. Každé takovéto sdílení totiž nabourává zapouzdření jednotlivých tříd a přináší tak riziko, že při některé z budoucích úprav zapomenete na nějakou důležitou závislost a zanesete do programu chybu. V naší knihovně tvarů by např. nebylo nejmoudřejší, kdyby třída Plátno zpřístupnila ostatním třídám svůj atribut jedináček, v němž uchovává odkaz na používané plátno. Třídy by sice nemuseli pokaždé o tento odkaz žádat, avšak při jakékoliv budoucí úpravě třídy Plátno bychom se vystavovali riziku, že změnu chování tohoto atributu zapomeneme přenést do všech tříd, které jej používají. Dohodněme se proto, že ne-konstantní atributy budeme dále deklarovat jako soukromé a jako přátelské budeme deklarovat pouze pomocné konstanty a metody, které mají své omezené použití pouze v rámci svého balíčku. 9.7 Tvorba vlastních aplikací V podkapitole Vytvoření samostatné aplikace na straně 168 jsme si říkali, jak je možno uložit vytvořenou aplikaci tak, aby ji pak bylo možno spouštět obdobně, jako jiné programy. Připomenu jenom, že jsem si tehdy říkali, že Java ukládá aplikace do souborů s příponou JAR, což jsou obyčejné ZIP soubory obsahující vedle souborů aplikace ještě soubory s pomocnými informacemi. Při tvorbě aplikací využívajících balíčky je třeba mít na paměti, že BlueJ zabalí do JAR souboru veškerý obsah stromu balíčků. Bude-li proto součástí aplikace pouze část stromu balíčků, musíme tuto část nejprve zkopírovat do nové, prázdné složky a teprve tuto složku exportovat do soubory JAR. Nesmíme přitom zapomenout zkopírovat příslušné složky včetně jejich rodičovských složek a do kořenové složky. Nejlépe si vše ověříte tak, že celou aplikaci po zkopírování přeložíte a zkusíte spustit. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 390 z 433 Kapitola 9: Budete si to přát zabalit? 9.8 391 Knihovny Občas se stane, že nepotřebujete uložit celou aplikaci, ale hodilo by se vám uložit pouze nějaký balíček nebo skupinu balíčků, které ve svých projektech používáte. 9.9 Shrnutí – co jsme se naučili V této kapitole jsme se seznámili s balíčky, jejich funkcí a použitím. Nyní byste již měli vědět, že: Do jednoho balíčku patří třídy, jejichž zdrojové, resp. přeložené soubory jsou ve stejné složce. Balíčky tvoří hierarchickou strukturu, která odpovídá struktuře složek. Každé vývojové prostředí definuje vlastní způsob jak označit kořenovou složku stromu balíčků. Název balíčku sestává z vlastního názvu balíčku, kterému předchází název rodičovského balíčku oddělený tečkou. Vlastní název balíčku je shodný s názvem složky obsahující třídy patřící do tohoto balíčku. Název složky proto musí odpovídat pravidlům pro tvorbu identifikátorů. Podle konvence se pro názvy balíčků používají pouze malá písmena. Příslušnost třídy k balíčku musíme definovat v příkazu package. Příkaz package musí být úplně prvním příkazem v souboru před nímž smějí předcházet pouze komentáře. Úplný název třídy je dán názvem balíčku následovaným tečkou a vlastním názvem třídy. Chceme-li pracovat s třídami z jiných balíčků a musíme buď používat jejich úplné názvy, nebo musíme jejich názvy nejprve dovézt pomocí příkazu import. Před příkazem import smí předcházet pouze příkaz package nebo jiný příkaz import. Příkaz import sestává z klíčového slova import následovaného úplným názvem dovážené třídy a středníkem. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 391 z 433 392 Myslíme objektově v jazyku Java 1.5 Nahradíme-li název třídy (tj. část úplného názvu za poslední tečkou) hvězdičkou, dovezeme názvy všech tříd z uvedeného balíčku. Podbalíček nepatří do balíčku. Dovezením všech názvů tříd v daném balíčku ještě nedovážíme třídy jeho podbalíčků – ty potřebují vlastní příkaz import. Jediné dva balíčky, které není třeba importovat, jsou systémový balíček java.lang a implicitní, bezejmenný balíček odpovídající kořenové složce stromu balíčků. BlueJ se k balíčkům (přesněji ke stromům balíčků) chová jako k samostatným projektům. Pro BlueJ je kořenovým balíčkem balíček, jehož složka obsahuje soubor bluej.pkg, avšak její rodičovská složka jej již neobsahuje. Implementace rozhraní z jiného balíčku není v BlueJ možno definovat natažením příslušné šipky, ale musí se zadat do zdrojového kódu „ručně“. V projektu není možno přímo vytvořit instance tříd z jiných balíčků, tj. vytvořit je zavoláním jejich konstruktoru. Tyto instance lze vytvořit např. pomocí speciální přemosťovací třídy se sadou statických metod zprostředkujících volání příslušných konstruktorů a statických metod. Neuvedeme-li u metody či atributu žádný modifikátor přístupu, bude tato metoda (atribut) považována za soukromou v rámci balíčku, tj. budou k ní mít přístup pouze třídy z daného balíčku. Nové termíny @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 392 z 433 Kapitola 10: Knihovny 10. 393 Knihovny Kapitola 10 Knihovny ☯ Co se v kapitole naučíme Tuto kapitolu bychom mohli označit za opakovací. Měli bychom si vyřešit pár úloh, na nichž si především zopakujeme, co jsme doposud probrali. 10.1 Pracujeme s náhodou Příklad: Brownův pohyb molekul 10.2 Návrhový vzor Adaptér Příklad: robot @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 393 z 433 394 Myslíme objektově v jazyku Java 1.5 10.3 Pracujeme s obrázky 10.4 Přetypovávání na rodiče a na potomka Na počátku kapitoly jsem říkal, že potomek je speciálním případem rodiče a může se proto v programu kdykoliv vydávat za instanci svého rodiče. 10.5 Shrnutí – co jsme se naučili Teze Nově zavedené termíny Termín @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 394 z 433 Část 3: Přemýšlíme @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 395 z 433 396 11. Myslíme objektově v jazyku Java 1.5 Program začíná přemýšlet Kapitola 11 Program začíná přemýšlet ☯ Co se v kapitole naučíme Text Příklady: Definujme třídu UFORot, jejíž instance se po opuštění vesmíru na jedné straně se vrátí na druhé straně. Definujme třídy robotů, kteří soutěží o vysbírání co největšího počtu značek. Definujeme ovladač klávesnice - Piškorky 11.1 Jednoduché rozhodování Naše dosavadní programy prováděly akci za akcí a nijak nepřemýšlely nad důsledky svého počínání. Většina úloh byla sice navržena tak, aby instance nemusely přemýšlet nad tím, co dělají, ale někdy by nás trocha toho přemýšlení potěšila. Jako příklad aplikace, kde by nám naše instance mohly trochu vyjít vstříc, by mohla sloužit naše z klávesnice ovládaná UFO. Pokud jsme včas a správně nezareagovali, UFO odletělo mimo viditelnou část vesmíru a jen velice těžko se nám podařilo je dostat zpět. Pokusme se tuto nepříjemnou vlastnost našich UFO trochu zmírnit. Definujme novou třídu, jejíž instance se budou pohybovat ve vesmíru, který je „navinut na kouli“. Jakmile UFO opustí naše zorné pole v jednom směru, objeví se vzápětí na opačném konci okna. UFO se nám tak přestanou ztrácet, protože vždy, když nám utečou, tak se vzápětí objeví někde jinde. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 396 z 433 Kapitola 11: Program začíná přemýšlet 397 Aby se mohlo UFO takto chovat, musí umět po každém posunu podívat na svoji pozici a podle ní se rozhodnout o svém případném přemístění za opačný okraj okna vesmíru. K takovémuto rozhodování slouží v Javě příkaz if. Tento příkaz sestává z klíčového slova if následovaného závorkami s testovanou podmínkou. Za závorkami pak následuje příkaz, který se má provést v případě, že je podmínka splněna. Není-li splněna, příkaz se jednoduše přeskočí. Ukažme si vše na naší hře s parkováním UFO. Vytvořme třídu RotUFO (rotující UFO), která bude dceřinou třídou naší třídy UFO. Její instance se budou chovat naprosto stejně jako instance rodičovské třídy s jediným rozdílem: utečou-li z okna zobrazujícího viditelnou část vesmíru, objeví se vzápětí na jeho opačném konci. Přesouvání instancí má na starosti metoda popojeď(int). Pro splnění našeho úkolu nám stačí tuto metodu překrýt. Vše ostatní může zůstat při starém. Jediná další věc, kterou budeme muset definovat, je konstruktor a i ten by mohl přenechat všechny starosti o vytvoření instance konstruktoru rodičovské třídy. Zamysleme se nad tím, jak by měla překrývající verze metody popojeď(int) vypadat. Na počátku by mohla zavolat rodičovskou verzi metody, která nastaví nové hodnoty rychlosti a pozice instance. Pak se podívá, jestli instance neutekla z okna a pokud ano, změní její pozici tak, aby se po příštím přesunu objevila na opačném konci okna. K tomu, aby metoda poznala, jestli instance opustila okno vpravo nebo dole, potřebuje znát šířku a výšku okna vesmíru. Na to se musí zeptat instance vesmíru. Protože ale tato velikost zůstává neměnná po celou dobu běhu aplikace, můžeme ji deklarovat jako konstantu třídy. Nebudeme instanci přesouvat hned, jak se dotkne okraje okna, ale počkáme až za okrajem bezpečně zmizí. Můžeme se např. rozhodnout, že ji přesuneme za opačný konec okna až poté, co se přesune za okraj okna o větší vzdálenost, než je velikost talíře. Abychom se nemuseli pořád ptát talíře na jeho rozměr, můžeme i tuto hodnotu definovat jako konstantu. Výsledná podoba definice třídy by mohla vypadat následovně: package rup.česky._10_přemýšlí; import rup.česky._03_třídy.ufo.*; public class RotUFO extends UFO { //== KONSTANTNÍ ATRIBUTY TŘÍDY ================================================= private final Vesmír V = Vesmír.getVesmír(); protected final int VŠÍŘKA = V.getŠířka(); protected final int VVÝŠKA = V.getVýška(); protected static final int TV = Talíř.VELIKOST; @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 397 z 433 398 Myslíme objektově v jazyku Java 1.5 //== KONSTRUKTORY A TOVÁRNÍ METODY ============================================= public RotUFO (Talíř talíř, int pořadí) { super( talíř, pořadí ); } //== PŘEKRYTÉ KONKRÉTNÍ METODY RODIČOVSKÉ TŘÍDY ================================ public void popojeď( int frekvence ) { super.popojeď( frekvence ); if( xPos < -TV ) xPos if( xPos > (VŠÍŘKA + TV) ) xPos if( yPos < -TV ) yPos if( yPos > (VVÝŠKA + TV) ) yPos = = = = VŠÍŘKA + TV; -TV; VVÝŠKA + TV; -TV; talíř.setPozice( xPos, yPos ); číslo.setPozice( xPos, yPos ); } }//public class RotUFO extends UFO Budete-li chtít definici této tříd vyzkoušet, musíte dispečerovi sdělit, že místo třídy UFO, kterou má přednastavenu jako implicitní, bude používat vaši třídu. To mu sdělíte tak, že zavoláte jeho metodu setUFOClass(Class), které předáte jako parametr objekt vlastní třídy. Třídy jako objekty Jestli si dobře vzpomínáte, tak jsem na počátku učebnice říkal, že v objektovém programování je všechno objekt. Když všechno, tak i třída. Objekty tříd jsou instancemi třídy Class. Chcete-li získat odkaz na instanci objektu představujícího třídu, máte několik možností: Nejjednodušší z nich je napsat jméno třídy následované tečkou a klíčovým slovem class tak, jak jste to mohli vidět v předchozím programu. Druhou možností je zavolat metodu instance getClass(). Tuto metodu dědí všechny třídy od třídy Object, takže na ni musí každá instance umět reagovat. Třetí možností je použití metody forName(String), která je statickou metodou třídy Class a očekává jako svůj parametr úplné jméno třídy (tj. včetně balíčku), po jejímž objektu se ptáte. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 398 z 433 Kapitola 11: Program začíná přemýšlet 399 V našem předchozím programu jsme potřebovali provést po každém testu opravdu pouze jeden příkaz. Takováto situace je však spíše výjimečná. Většinou je potřeba provést příkazů více. V takovém případě se všechny tyto příkazy uzavřou mezi složené závorky. Tím vytvoří složený příkaz, který překladač chápe jako příkaz jediný. Složené závorky a jimi vytvořený složený příkaz můžeme samozřejmě použít i tehdy, když se má provést jediný příkaz (překladač počet příkazů uvnitř složených závorek nekontroluje). Kdybychom je použili, mohl by první podmíněný příkaz z předchozího programu vypadat např. následovně: if( xPos < -TV ) { xPos = VŠÍŘKA + TV; } 11.2 Výběr ze dvou možností 11.3 Když – jinak 11.4 Násobný výběr, 11.5 Přepínač 11.6 Shrnutí – co jsme se naučili Teze @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 399 z 433 400 Myslíme objektově v jazyku Java 1.5 Nově zavedené termíny Termín @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 400 z 433 Kapitola 12: Ještě jednu rundu, prosím 12. 401 Ještě jednu rundu, prosím Kapitola 12 Ještě jednu rundu, prosím ☯ Co se v kapitole naučíme V této kapitole. 12.1 Podkapitola 12.2 Shrnutí – co jsme se naučili Nové termíny @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 401 z 433 402 13. Myslíme objektově v jazyku Java 1.5 Kontejnery nejsou jen na odpadky Kapitola 13 Kontejnery nejsou jen na odpadky ☯ Co se v kapitole naučíme Text 13.1 Podkapitola Text 13.2 Shrnutí – co jsme se naučili Teze Nově zavedené termíny Termín @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 402 z 433 Kapitola 14: Pole 14. 403 Pole Kapitola 14 Pole ☯ Co se v kapitole naučíme Text 14.1 Podkapitola Text 14.2 Shrnutí – co jsme se naučili Teze Nově zavedené termíny Termín @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 403 z 433 404 15. Myslíme objektově v jazyku Java 1.5 Jak nebýt výjimečný Kapitola 15 Jak nebýt výjimečný ☯ Co se v kapitole naučíme Text 15.1 Podkapitola Text 15.2 Shrnutí – co jsme se naučili Teze Nově zavedené termíny Termín @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 404 z 433 Kapitola 16: Co jsme si ještě neřekli 16. 405 Co jsme si ještě neřekli Kapitola 16 Co jsme si ještě neřekli ☯ Co se v kapitole naučíme Text 16.1 Podkapitola Text 16.2 Shrnutí – co jsme se naučili Teze Nově zavedené termíny Termín @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 405 z 433 Část 4: Přílohy @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 406 z 433 A Instalace vývojového prostředí Příloha A Instalace vývojového prostředí A.1 Instalace Java 2 SDK Vývojové prostředí pracuje na platformě Java. Abyste je mohli používat, musíte mít nejprve tuto platformu nainstalovánu. Protože je to vývojové prostředí a ne obyčejný program, požaduje instalaci takzvaného JDK (Java Development Kit – vývojová sada Java), což je platforma doplněná o základní vývojové nástroje (překladač, ladící program, generátor dokumentace a další). JDK si můžete zdarma stáhnout na stránkách firmy Sun na adrese http://java.sun.com. Nespleťte si JDK s JRE (Java Runtime Environment), které sice obsahuje kompletní balík programů potřebný pro spouštění aplikací v Javě, ale chybí mu právě takové drobnosti, jako překladač nebo generátor dokumentace, bez kterých nic nevyvinete. Současně dejte si pozor na to, abyste stáhli standardní edici označovanou J2SE. Vedle ní zde totiž najdete ještě J2ME určenou pro vývoj aplikací pro mobilní telefony a J2EE určenou pro vývoj distribuovaných aplikací, které jsou schopny běžet na několika počítačích komunikujících po síti. Na stránce si můžete vybrat mezi verzemi pro Windows a Linux. Počítejte však s tím, že stahovaný soubor má přibližně 50 MB (hovořím o verzi 1.4.2_04 – budete-li stahovat mladší verzi, bude určitě větší) a po rozbalení bude mít asi dvakrát tolik. Umíte-li anglicky a máte-li rozumně rychlé připojení, doporučuji vám navíc stáhnout na téže stránce i dokumentaci, avšak znovu vás musím připravit na to, @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 407 z 433 408 Myslíme objektově v jazyku Java 1.5 že stahovaný soubor má přes 30 MB po rozbalení bude samotná dokumentace zabírat na disku okolo 180 MB. Máte-li proto někde ve svém okolí někoho, kdo v tomto jazyku programuje (může to být i firma), bude možná výhodnější požádat jej o vypálené CD. Při té příležitosti vám může vypálit i nějaké vývojové prostředí, ve kterém budete pracovat poté, co BlueJ opustíte. JDK spolu s různými vývojovými prostředími se jednou za čas objeví také na CD, která jsou přibalena k některým počítačovým časopisům. Pracujete-li na počítači, kde je Java již instalovaná, ověřte si, že je na něm opravdu instalované JDK (někdy bývá označováno také jako SDK – Software Development Kit) verze 1.4 nebo novější. Budete-li v budoucnu pracovat s Javou, bude množství stažených a instalovaných programů rychle narůstat. Můžete mít např. instalovány různé verze JDK (jednu pro desktopové aplikace, druhou pro vývoj aplikací pro mobilní telefony, třetí pro vývoj aplikací pracujících na několika počítačích současně), různá vývojová prostředí (já vedle BlueJ, které používám v této učebnici, používám při své práci ještě další tři) a řadu nejrůznějších užitečných pomocných programů. Já jsem to na svém počítači vyřešil tak, že věci týkající se Javy neinstaluji do složky Program fines, kam se ve Windows programy standardně umisťují, ani do kořené složky, kam se řada z nich vnucuje (jsem nepřítelem zaplácané kořenové složky), ale vytvořil jsem v kořenovém adresáři složku Java, do které všechny tyto programy instaluji. Zabíjím tím několik much jednou ranou: Nemám programy Javy pomíchané s ostatními programy, takže je při inovaci snadno najdu (programy v Javě mívají velice krátký inovační cyklus). Nemám problémy se staršími programy, které neměly rády mezery v názvech složek. DOTAZ: Mám tady podrobně popsat stažení a instalaci nebo je to dostatečně jasné? Dokumentace bývá standardně umísťována do stejné složky jako zbytek JDK. V zazipovaném souboru je uspořádána do složek a podsložek, přičemž společnou rodičovská složka se jmenuje docs. Nezapomeňte, že při rozbalování se musíte toto uspořádání dodržet. Ve složce s JDK se proto po rozbalení vedle složek bin, lib a dalších, které tam vytvořil instalační program JDK objeví ještě složka docs s řadou podložek, v nichž bude rozmístěna veškerá dokumentace. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 408 z 433 Příloha A. Instalace vývojového prostředí A.2 409 Instalace prostředí BlueJ Pro komfortní vývoj programů je užitečné používat nějaké rozumně komfortní vývojové prostředí. V této učebnici budu používat porstředí BlueJ, které navržené speciálně pro výuku programování a které si můžete zdarma stáhnout na internetové adrese http://www.BlueJ.org/download/download.html. Není to již taková obluda jako JDK, poslední verze 1.3.5 má pro Windows pouhých 2,5 MB. Součástí standardní distribuce je i česká lokalizace. K tomuto prostředí je k dispozici i jednoduchý český tutoriál, který si však musíte stáhnout samostatně. Kdyby vám nestačilo to, co vám tu o prostředí, můžete si jej stáhnout např. na adrese http://java.pecinovsky.cz/BlueJ/BlueJ_Tutorial_Czech_RUP.pdf. Prostředí BlueJ budeme instalovat dvoufázově: v první etapě si nainstalujete vlastní prostředí a ve druhé pak instalaci doplníte o speciální rozšíření, které vlastnosti tohoto prostředí přizpůsobí požadavkům této učebnice. Instalace prostředí Instalace prostředí probíhá v postatě standardně. Instalační program se vás nejprve zeptá, zda chcete opravdu instalovat BlueJ a odpovíte-li, že ano, spustí průvodce instalací, který vás nejprve přivítá a pak vás postupně nechá zadat složku, do níž chcete BlueJ instalovat (doporučuji vám použít podobnou účelovou složku, jako je moje složka Java, o níž jsem vám vyprávěl při instalaci JDK), skupiny startovací nabídky do níž chcete umístit jeho zástupce a zeptá se vás, zda chcete umísti ikonu BlueJ na pracovní ploše. Předpokládám proto, že nepotřebuje nijaký podrobný výklad. Instalace rozšíření Když budete mít BlueJ instalováno, měli byste si instalovat ještě rozšíření, které si můžete stáhnout z adresy http://vyuka.pecinovsky.cz. Toto rozšíření je v zazipovaném souboru, který je třeba rozbalit do podsložky lib složky, do níž jste instalovali BlueJ. Máte-li tedy stejně jako já BlueJ instalovaný ve složce C:\Java\BlueJ, budete rozšíření rozbalovat do složky C:\Java\BlueJ\lib. Rozšíření nahradí jeden programový soubor jeho opravenou verzí, aktualizuje dva konfigurační soubory a přidá složku rup, která obsahuje českou lokalizaci a některé soubory, které budeme v učebnici využívat. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 409 z 433 410 Myslíme objektově v jazyku Java 1.5 První spuštění Po prvním spuštění program otevře dialogové okno BlueJ Launcher, v němž se vás zeptá na umístění JDK. V tomto dialogovém okně vypíše všechna JDK, která na vašem počítači identifikuje. Nalézá-li se mezi nimi to, s nímž chcete pracovat, klepněte na příslušný řádek a pak stiskněte tlačítko Launch BlueJ. Obrázek A.1 Zadání použitého JDK Někdy se stane, že BlueJ žádné JDK nenajde a okno bude prázdné, nebo v něm není to JDK, jež byste rádi použili. Pak stiskněte tlačítko Advanced, čímž rozbalíte spodní část okna, která nabízí další možnosti. Obrázek 16.2 Nalezení použitého JDK @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 410 z 433 Příloha A. Instalace vývojového prostředí 411 Předpokládám, že víte, kam jste JDK instalovali. Pak stačí stisknout tlačítko Browse for a specific Java version… a v následně otevřeném dialogovém okně pro otevření souboru nalézt požadované JDK. Počítejte ale s tím, že tentokrát se již BlueJ nespokojí se složkou, v níž je celé JDK instalováno (a kterou uvádí v horní části okna), ale bude chtít ukázat přímo soubor java.exe, který se nalézá v podsložce bin – při použití J2SDK 1.5.0 by to tedy na mém počítači byl soubor E:\Java\j2sdk1.5.0\bin\java.exe. Po úspěšném zadání JDK „obživne“ tlačítko Launch BlueJ. Stiskněte je a program dialogové okno zavře, po chvíli zobrazí své zaváděcí okno a po další chvíli se BlueJ natáhne a spustí. S výše popsaným dotazem vás již příště obtěžovat nebude. Pokud však své JDK později inovujete, je možno BlueJ přesvědčit, aby je začal používat. Ve startovací nabídce zadáte ve složce BlueJ příkaz Select VM, čímž znovu otevřete výše popsaná okna, která vám umožní požadovanou verzi JDK zadat. Konfigurace BlueJ Než začnete BlueJ doopravdy používat, měli byste si jej nekonfigurovat, aby vám generoval podobné obrázky, jako ten, který používám já. Zadejte proto povel Nástroje → Nastavení. BlueJ otevře dialogové okno, v němž můžete zadat většinu potřebných parametrů. Dialogové okno má čtyři karty, z nichž se nyní podíváme pouze na první dvě. Na kartě Editor vám doporučuji zaškrtnout všechny volby tak, jak to vidíte na obrázku. Velikost písma si nastavte tak, aby vám maximálně vyhovovala. Podle rozlišení obrazovky se bude optimální velikost pohybovat pravděpodobně mezi 10 a 12 body. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 411 z 433 412 Myslíme objektově v jazyku Java 1.5 Obrázek 16.3 Karta Editor Na kartě URL vám taktéž doporučuji zaškrtnout všechna políčka. Do vstupního pole URL dokumentace Java API pak zadejte cestu k dokumentaci dodávanou k JDK. Pokud jste dokumentaci umístili tak, jak jsem vám radil, tak v tomto poli zadáte cestu k souboru index.html umístěném v podložce api složky docs (připomínám, že docs je složka, v níž je umístěna veškerá dokumentace). Pokud jste dokumentaci neinstalovali, tak nic nezadávejte, ale musíte pak zrušit zaškrtnutí políčka Použít toto URL při generování projektové dokumentace. Obrázek 16.4 Karta Různé @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 412 z 433 Příloha A. Instalace vývojového prostředí @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 413 Strana 413 z 433 414 B Myslíme objektově v jazyku Java 1.5 Základy práce s BlueJ Příloha B Základy práce s BlueJ Základní vlastnosti zabudovaného editoru Autoři editoru dodávaného s prostředím BlueJ se snažili vytvořit program maximálně jednoduchý, aby žáci zvládli jeho ovládání co nejrychleji, avšak na druhou stranu dostatečně komfortní, aby jim tvorbu jejich programů co nejvíce usnadnil. Mezi základní „komfortní doplňky“, které v současné době patří již k téměř povinné výbavě všech programátorských editorů, patří možnost automatického odsazování, barevného zvýraznění syntaxe, vytváření záložních kopií, zobrazování čísel řádků a schopnost dohledání a označení párové závorky k závorce, u které je textový kurzor. Navíc nám zabudovaný editor umožňuje nastavit velikost použitého písma (jinou použijeme při vlastní tvorbě a jinou pak při její prezentaci před skupinou posluchačů). Všechny tyto vlastnosti můžeme nastavit či naopak potlačit v dialogovém okně, jež vyvoláme příkazem Předvolby → Nastavení…. Obrázek Chyba! V dokumentu není žádný text v zadaném stylu..5: Nastavení předvoleb pro editor a překladač. Když už jsme toto dialogové okno otevřeli, povíme si i o ostatních možnostech, které jdou na kartě Různé nastavit. Prvním z nich je povolení novinek z JDK 1.4. Verze 1.4 totiž zavedla nové klíčové slovo assert. Jeho použití je však implicitně zakázáno pro případ, že by je někdo ve svých starších programech již použil jako identifikátor. Kdo však ví, že slovo assert ve svých programech nikdy nepoužil, může je povolit. Protože my vytváříme programy zcela nové, rádi jeho použití povolíme, abychom později mohli využít jeho příjemných vlastností. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 414 z 433 Příloha B. Základy práce s BlueJ 415 Druhou možností je nastavení adresy s dokumentací standardní knihovny. Po instalaci je zde nastavena internetová adresa této dokumentace na stránkách firmy Sun. Pokud jste spolu s JDK instalovali i jeho dokumentaci, doporučuji vám přepsat tuto adresu na lokální adresu ve vašem počítači. Na obrázku Chyba! V dokumentu není žádný text v zadaném stylu..5 si všimněte, že se zde nastavuje absolutní cesta k souboru index.html ve složce api, jež je podložkou složky, do níž jste dokumentaci instalovali. Pokud jste se zalekli rozměru dokumentace a neinstalovali jste ji, zrušte zaškrtnutí políčka Použít toto URL při generování projektové dokumentace. Jedinou výhodou, o níž se zrušením tohoto zaškrtnutí připravíte, je to, že do vaši dokumentace nebudou vloženy odkazy na dokumentaci souvisejících částí standardní knihovny. Až si budeme povídat o dokumentaci, ještě se k tomu vrátím. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 415 z 433 416 C Myslíme objektově v jazyku Java 1.5 Syntaktické diagramy Příloha C Syntaktické diagramy @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 416 z 433 Příloha D. Použité projekty D 417 Použité projekty Příloha D Použité projekty Všechny dále popisované projekty jsou připraveny ke stažení na internetové adrese http://vyuka.pecinovsky.cz. Odtud byste si je měli všechny stáhnout před tím, než se pustíte do studia. 02_Objekty Úvodní projekt, v němž se čtenář seznámí s pojmy třída a objekt a naučí se interaktivně vytvářet instance, volat metody tříd a instancí a prohlížet si jejich atributy. 03_Třídy_A Výchozí verze projektu, na kterém se učíme psát vlastní definice tříd. Tento projekt obsahuje stejné třídy jako předchozí projekt. Změna je v jejich upraveném uspořádání snažícím se o lepší sledovatelnost čar závislostí a v přidání pomocné třídy P obsahující některé užitečné metody. 03_Třídy_Z Závěrečná verze projektu obsahující oproti projektu 03_Třídy_A všechny třídy, které jsme vytvořili v průběhu 3. a 4. kapitoly. 03_UFO Závěrečný projekt třetí kapitoly v němž má čtenář za úkol naprogramovat třídu UFO, která bude součástí rozsáhlejší aplikace realizující jednoduchou hru. V této hře má hráč za úkol dovést jednotlivé instance třídy UFO ze startovací rampy do hangárů. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 417 z 433 418 Myslíme objektově v jazyku Java 1.5 04_Zlomky Závěrečný projekt čtvrté kapitoly, v němž má čtenář za úkol vytvořit třídu Zlomek umožňující zlomkovou aritmetiku. Projekt obsahuje i předpřipravené vzorové řešení a testovací programy pro vzorovou i uživatelem vytvořenou třídu. 05_Vzory Projekt navazuje na projekt 03_Třídy_Z. Doplňuje jej o definici tří přepravek a upravuje definice tříd tak, aby mohli přepravek využít. Navíc obsahuje třídu definující jedináčka a třídy definující výčtové typy. 06_Rozhraní_A Úvodní projekt šesté kapitoly přináší nově koncipovanou sadu základních tříd geometrických tvarů, jejíž hlavní změnou je výměna třída Plátno za třídu AktivníPlátno a z ní vyplývající úpravy ostatních tříd. 06_Rozhraní_Z Projekt, který je rozšířením předchozího projektu o všechny třídy, které jsme v průběhu šesté kapitoly zavedli nebo definovali. 07_Dědění_rozhraní_Z Projekt obsahující třídy ve stavu, ke kterému dospějeme na konci kapitoly a dědičnosti rozhraní. Zavádí dědičnost rozhraní a z toho plynoucí výhody a zavádí také třídu Multipřesouvač a rozhraní IMultiposuvný. Současně v něm najdete vyřešenou úlohu a lanovkou a jejími kabinkami. 08_Dědičnost_tříd_pokusy Pomocný projekt sloužící při výkladu základních zákonitostí dědičnosti tříd. 08_Dědičnost_tříd_A Výchozí projekt kapitoly pojednávající o dědičnosti tříd. Obsahuje třídy grafických objektů s atributy AP definovanými jako protected, avšak prozatím bez společné rodičovské třídy. @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 418 z 433 Příloha D. Použité projekty 419 08_Dědičnost_tříd_B Předchozí projekt rozšířený o třídy, které vytvoříme do chvíle, než definujeme společnou rodičovskou třídu pro všechny grafické třídy. 08_Dědičnost_tříd_C Předchozí projekt po definici společné rodičovské třídy pro všechny třídy implementující rozhraní IPosuvný. 08_Dědičnost_tříd_D Předchozí projekt po definici společné rodičovské třídy pro všechny třídy implementující rozhraní IHýbací. 08_Dědičnost_tříd_E Předchozí projekt po převodu společných rodičovských tříd Posuvný a Hýbací na abstraktní třídy APosuvný a AHýbací. @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 Strana 419 z 433 Rejstřík Rejstřík —%— —Č— %, 102, 184 číslo, 180 celé, 147 desetinné, 146 —A— Ada, 20 analogie, 17, 43, 47, 57, 65, 67, 68, 72, 128, 130, 244, 265, 324, 325, 332, 333 aplikace soubor JAR, 169 spouštějící třída, 169 vytvoření, 168 argument. viz parametr arita, 182 assertEquals, 208 assertTrue, 208 atribut, 68 instance, 68 jen pro čtení, 129 kvalifikace, 134 možné důsledky zveřejnění, 112 třídy, 68, 70, 136 veřejný zadat jako parametr, 73 atributy deklarace, 109 —B— Babbage, 19 běhová chyba, 95, 97 bílý znak, 94 BlueJ, 28 projekt otevření, 32 boolean, 146 —C— class-soubor, 86 —D— debugger, 211 objektové atributy a proměnné, 218 pozastavení programu, 221 dědičnost, 246 definice, 81, 171 syntaktická, 113 deklarace, 81, 171 dekrementace --, 191 dělení celočíselné (/), 183 modulo (%), 184 zbytek, 102 design pattern publikace, 229 Design Pattern, 229 diagram tříd, 33, 44 manipulace s třídami, 34 disk substituovaný, 30 dokumentace, 149 automaticky generovaná, 160 projektu, 161 tagy. viz dokumentace – značky značky, 164 double, 146 —E— exponent, 147 —G— gang čtyř, 229 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 garbage collector. viz správce paměti GoF, 229 —H— halda, 66 historie, 19 hlavička konstruktoru, 92 hlavička třídy, 85 hodnota návratová, 55 objektového typu, 126 odkaz, 75 vstupní, 58 výstupní, 55 — Ch — chyba běhová, 95, 97 logická, 95, 99 sémantická. viz chyba logická syntaktická, 95, 96 —I— IDE, 28 identifikátor pravidla pro tvorbu, 46 implementace, 242 versus rozhraní, 128 implementace rozhraní, 250 inkrementace ++, 191 instance, 41 atribut, 68 odkaz, 56 počítání, 190 prohlížeč, 56 prohlížení, 68 rodná čísla, 190 rozhraní, 244 versus odkaz, 66 vytvoření, 44, 47, 50 Strana 420 z 433 16. Co jsme si ještě neřekli zrušení, 50 int, 146 interface, 240 interpret, 23, 24 —J— JAR soubor, 169, 390 Java, 25 jazyk hybridní, 24 programovací, 24 UML, 33 JDK, 27 Jedináček, 233 JRE, 27 —K— klíčové slovo public, 85 kód zdrojový, 84 komentář, 149 dokumentační, 149 obecný, 149 řádkový, 149 kompilátor. viz překladač konstanta, 143 pojmenovaná, 143 konstruktor, 43 bezparametrický, 91 definice, 91 hlavička, 61, 92 implicitní, 87 s parametry, 59, 100 this, 101 kontrakt, 129, 132 krokování programu, 212 kvalifikace, 132 implicitní, 133 this, 133 kvalifikace atributů, 134 kvalifikace metod, 132 —L— ladění, 95 literál, 143 boolean, 146 double, 146 int, 146 logická chyba, 95, 99 lokální proměnná, 139 —M— mantisa, 147 Messenger, 230 metoda, 42 421 assertTrue, 208 definice, 115 kvalifikace, 132 přístupová, 129 konvence pro názvy, 131 statická. viz metoda třídy, viz metoda třídy test, 117 toString, 198 třídy, 64, 136, 137 vracející hodnotu použití, 124 metoda assertEquals, 208 mezera sémantická, 22 modifikátor přístupu, 110 private, 110 protected, 329 public, 85, 110 modulární programování, 22 —N— nápověda komentářová, 158 návratová hodnota objektového typu, 126 návrhový vzor, 229 jedináček, 233 Služebník, 266 Stav, 298, 312 Zástupce, 304 název. viz identifikátor new, 43 null, 148, 179 —O— objekt, 41 stav, 68 objektově orientované programování. viz OOP odkaz, 66 jako návratová hodnota, 75 zásobník odkazů, 47 OOP, 21 principy, 22 operace, 182 operační systém, 23 operand, 182 operátor, 182, 185 binární, 182 dekrementační --, 191 dělení (/), 183 inkrementační ++, 191 modulo (%), 184 @Java_PO.doc, verze 0.33.197, uloženo: úterý 29.června.2004 – 23:55 násobení (*), 182 new, 43 odčítání (–), 182 přetypování, 188 přiřazení sdružený (+=, –=, *=, /=, %=), 187 přiřazovací (=), 186 sčítání (+), 182 slučování řetězců (+). viz String – operátor slučování ternární, 182 unární, 182, 191 unární –, 185 unární +, 185 —P— parametr, 58, 59, 100 objektového typu, 62, 126 přímé zadání hodnoty objektového typu, 62 primitivního typu, 59 přímé zadávání, 73 platforma, 24 private, 69, 110 program definice, 41 interpretovaný, 24 krokování, 212 překládaný, 24 ukončení ruční, 214 programovací jazyk, 24 programování modulární, 22 objektově orientované, 21 strukturované, 22 prohlížeč instancí, 56 projekt, 29 otevření v BlueJ, 32 umístění na disku, 29 proměnná lokální, 139 protected, 329 překlad, 34, 86 překladač, 23, 24 Přepravka, 230 přetěžování, 58, 127 přetížená verze, 58 přetypování, 188 pseudopřetypování na String, 189 přípravek, 106 úprava obsahu, 107 přiřazení, 186 sdružené, 187 přístupová metoda, 129 konvence pro názvy, 131 Strana 421 z 433 422 public, 85, 110 Myslíme objektově v jazyku Java 1.5 operační, 23 —R— —Š— refaktorování, 272 rozhraní, 240 dědění několika, 291 implementace odvolání, 265 implementace několika, 264 implementace rozhraní, 250 instance rozhraní, 244 stavové, 299, 312 versus implementace, 128 šipka dědičnost, 246 implementační, 246 používá, 246 —Ř— řetězec. String textový. viz String —S— SDK, 27 sdružené přiřazení, 187 sémantická chyba. viz logická chyba sémantická mezera, 22 Singleton, 233 slovo klíčové. viz klíčové slovo Služebník, 266 soubor JAR, 169, 390 správa paměti, 50 správce paměti, 51, 66 Stav, 298, 312 stavové rozhraní, 299, 312 stereotyp, 105 String, 147 operátor slučování (+), 183 prázdný řetězec, 179 přechod na nový řádek, 148 rozdělení dlouhého textu, 147 slučování řetězců, 177 stroj virtuální, 24, 26 restartování, 90 strukturované programování, 22 substituovaný disk, 30 syntaktická definice, 113 syntaktická chyba, 95, 96 syntaxe, 113 systém —T— TDD, 104, 120 terminologie, 16, 42, 51, 55, 58, 68, 71, 81, 83, 86, 90, 115, 116, 131, 133, 136, 143, 144, 148, 182, 191, 243, 246, 267, 317, 318, 325, 333, 355, 364, 365, 389 test automaticky generovaný, 206 testů, 209 úklid po testu, 208 vlastní, 207 Test Driven Development, 104 testovací třída, 105 testování, 104 textový řetězec. viz String this konstruktor, 101 kvalifikace, 132, 133, 134 this.atribut, 132 toString, 198 třída, 41 class-soubor, 86 hlavička, 85 nová vytvoření, 82 odstranění, 88 prázdná, 89 standardní, 199 překlad, 86 testovací, 105 uspořádání prvků v těle, 157 zdrojový kód, 84 zkopírování z disku, 177 tvar semilogaritmický, 147 typ boolean, 53 datový, 52 double, 53 hodnotový, 222 int, 53 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 neměnný, 222 objektový, 53 parametry, 62 vracení hodnot, 55 primitivní, 53 vracení hodnot, 54 proměnný, 222 referenční, 223 String, 54 typu referenční, 222 —U— úklid po testu, 208 úloha, 17, 49, 50, 52, 62, 64, 65, 66, 70, 73, 97, 101, 104, 109, 112, 117, 120, 122, 134, 136, 139, 141, 143, 191, 193, 199, 225, 231, 235, 261, 264, 266, 272, 280, 288, 296, 306, 338, 343, 344, 350, 351, 355, 363 UML, 33, 44 —V— velbloudí notace, 109 virtuální stroj, 24, 26 restartování, 90 VM. viz stroj virtuální vstup jednoduchý, 176 výstup jednoduchý, 176 standardní, 196 vzor návrhový, 229 vzor návrhový Zástupce, 304 —Z— zapouzdření, 128 zarážka, 212 zrušení, 220 zásobník odkazů, 47, 62 Zástupce, 304 zbytek po dělení, 102 zdrojový kód, 84 znak bílý, 94 zpráva, 41 s parametry, 63 zaslání instanci, 49 žádající o hodnotu, 52 Strana 422 z 433 @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 423 z 433 Část 5: KONEC @Java_PO.doc, verze 0.33.197, uloženo: út 29.6.04 – 23:55 Strana 424 z 433
Podobné dokumenty
Vážení studenti 1
31.8.2012 na adresu uvedenou v záhlaví. Platba může být realizována přiloženou složenkou nebo
zasláním na účet ČNB Ostrava 931761/0710, variabilní symbol 317632.
Originál dokladu o uhrazení částky ...
výuka programování podle metodiky design patterns first
Mají-li si žáci nějakou látku důkladně osvojit, není možné začít s jejím výkladem až někdy
před koncem příslušného kurzu. Tato zásada je obzvlášť důležitá u předmětů, u nichž nestačí
se látku pouze...
Současné trendy v metodice výuky programování
neobjektových zkušeností (jak také, když žádné nemají) a naopak lépe odpovídá jejich dosavadním návykům z reálného světa.
Naproti tomu ti, kteří již programovali a chtějí
pouze zvládnout novou tech...
Použití CASE pro řízení IS/ICT firmy
PowerDesigner Viewer je určen nikoliv samotným analytikům, ale ostatním uživatelům
výsledků jejich práce. PowerDesigner Viewer umožňuje prohlížení všech typů modelů
vytvořených v PowerDesigneru a p...
Nástroje pro vývoj aplikací a jejich vazba na CASE
Na rozdíl od předchozích verzí, ECO III je možno využívat pro vývoj aplikací ve více než
jednom jazyce, protože např. datové typy definuje jednotně, a až následně je převádí na
specifické pro daný ...
Nástroje pro vývoj aplikací v závislosti na platformě a jejich vazba na
Od vývoje UML, který započal v roce 1994, byly vydány dvě hlavní verze – verze 1.0, vydána v roce
1997 a verze 2, vydána v roce 2005. V současné době je vydána verze 2.2 a ve fázi vývoje je verze 2...
PDF verze ke stažení - Objektově orientované programování a jeho
• Objekt třídy není instancí žádné třídy tříd (hovořím o jazyku Java, v jiných jazycích – např. ve Smalltalku
– to může být jinak). V programu jej musíme vždy oslovovat přímo. V Javě je v některých...