Nově zavedené termíny

Transkript

Nově zavedené termíny
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

Java 5.0 - SworkTech.com

Java 5.0 - SworkTech.com definice vlastních anotací a možnosti jejich využití při běhu

Více

Vážení studenti 1

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íce

výuka programování podle metodiky design patterns first

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...

Více

Současné trendy v metodice výuky programování

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...

Více

Použití CASE pro řízení IS/ICT firmy

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...

Více

Nástroje pro vývoj aplikací a jejich vazba na CASE

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ý ...

Více

Nástroje pro vývoj aplikací v závislosti na platformě a jejich vazba na

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...

Více

PDF verze ke stažení - Objektově orientované programování a jeho

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...

Více