Učebnice jazyka Java na webových příkladech pro úplné začátečníky
Pavel Ponec
jbook.ponec.net/cs.html
Jazyková korektura: Ing. Vladimíra Krejčíková
Ilustrace obálky: Marie Brogowski
Vnitřní ilustrace: autor knihy (pokud není uvedeno jinak)
Vydavatelství: powerprint, Praha
Tisk: powerprint s. r. o.,
Brandejsovo nám. 1219/1, 165 00 Praha – Suchdol
www.bookla.cz
ISBN 978-80-7568-271-0
Vydání první, 2020
Poděkování
Rád bych poděkoval své laskavé dceři za mnoho užitečných podnětů a připomínek, bez kterých by tato kniha neměla současnou podobu. Poděkování patří také všem mým dlouhodobým kolegům v týmu za celou řadu užitečných rad, nápadů a konzultací. Kniha byla vytvořena pomocí open‑source aplikací včetně ukázkových příkladů, vektorových obrázků a nástrojů pro sestavení výsledku – to vše na svobodném operačním systému Linux. Tímto bych rád vyjádřil také svůj obdiv a poděkování široké komunitě lidí, kteří se starají o vývoj, údržbu a distribuci open‑source projektů.

1. Úvodní informace
V této kapitole jsou uvedeny některé důležité informace, které však nemají přímý vliv na porozumění technickému výkladu.
1.1. Ochranné známky
Oracle, Java a VirtualBox jsou registrované ochranné známky společnosti Oracle Corporation anebo jejích poboček. Autor knihy nemá žádnou vazbu ke společnosti Oracle. Apache Maven, Apache Tomcat, Apache NetBeans a NetBeans jsou ochranné známky neziskové organizace Apache Software Foundation (ASF) [25]. Ubuntu, Xubuntu a Canonical jsou registrované ochranné známky společnosti Canonical Ltd. Microsoft a Windows jsou registrované ochranné známky společnosti Microsoft Corporation v USA anebo jiných zemích. Docker je registrovaná ochranná známka společnosti Docker, Inc. v USA anebo jiných zemích. Docker, Inc., a také další strany mohou mít práva k ochranné známce. Eclipse a Jetty jsou registrované ochranné známky společnosti Eclipse Foundation, Inc. ve Spojených státech a případně v dalších jiných zemích.
1.2. Vyloučení odpovědnosti
Mějme na paměti, prosím, že vzorové ukázky programového kódu byly vytvořeny za účelem výuky. Naznačují koncept, ale obsahují některá zjednodušení, například důsledné ošetřování vyhozených výjimek je z důvodu stručnosti omezené, a proto ukázky nejsou určeny přímo k produkčnímu použití.
I když autor věnoval značné úsilí odstraňování chyb a překlepů v textu, nelze vyloučit jejich přehlédnutí. Opravy, které byly zaregistrovány po vydání knihy, najdeme na její domovské stránce [1], kde lze nahlásit případné nesrovnalosti ve výkladu či jiné připomínky. Děkuji za všechny konstruktivní podněty.
1.3. Licence ukázkových příkladů
Po zakoupení knihy si prosím uschovejte doklad o nákupu, protože vás opravňuje používat ukázkové příklady k soukromým i komerčním účelům. Distribuce zdrojového kódu k této knize je možná pouze při zachování původního komentáře v hlavičce, zároveň je nezbytné, aby třetí strana získala neprodleně licenci pro studium, modifikaci nebo distribuci zakoupením knihy nebo e‑booku. V případě nejasností se obracejte na vydavatele knihy nebo přímo na autora, kontakty najdete na internetové stránce knihy [1].
Zvažte, prosím, že výroba knihy není jedinou nákladovou položkou, další výdaje souvisí s propagací a distribucí. Děkuji tímto za podporu i pochopení.
1.4. Typografie
Slova psaná kurzívou budou označovat názvy produktů a nové technické termíny.
Kvůli jejich vzrůstajícímu počtu však tyto termíny nebudou vyznačovány v celém rozsahu knihy, ale jen ve vybraných částech pro zdůraznění jejich specifického významu.
Slova psaná neproporcionálním
písmem (znaky s pevnou šířkou) značí programové výrazy a příkazy, klávesy nebo názvy souborů.
V ukázkách zdrojového kódu jsou
klíčová slova jazyka Java vyznačena modře (●),
třídy objektů zeleně (●) a literály červeně (●).
2. Úvod a motivace
Write once, run anywhere.
americká společnost
Od roku 1991 usilovala firma Sun Microsystems o vytvoření jednoduchého, robustního, objektově orientovaného programovacího jazyka, jehož aplikace by byly nezávislé na platformě zařízení, hlavní myšlenku reprezentoval slogan v záhlaví této kapitoly. Vedoucím projektu a původním autorem byl kanadský softwarový programátor James Gosling. Roku 1996 vydala firma první oficiální verzi jazyka pod názvem Java a o jedenáct let později (roku 2007) uvolnila většinu zdrojových kódů pod licencí open‑source. Počátkem roku 2010 proběhla akvizice firmy Sun Microsystems společností Oracle, která se tak stala vlastníkem oficiální implementace Java SE platformy.
2.1. Proč vznikla tato kniha?
Když jsem kdysi dávno začínal s jazykem Java, kladl jsem si otázku, zdali není možné napsat učebnici nějakým více atraktivním způsobem. Důležité zkušenosti a nadhled jsem začal nabývat až praxí. Čas plynul a po mnoha letech jsem zveřejnil krátký článek o sestavování webových stránek v jazyce Java, postavených na objektovém modelu HTML elementů, a tehdy mě napadlo, že tohle by mohl být skvělý prostředek pro tvorbu interaktivních učebnicových příkladů při studiu jazyka. V nejbližším knihkupectví jsem otevřel jednu z nových učebnic tohoto jazyka a spatřil opět formální popis s černobílými obrázky, akademickými příklady a několika málo grafickými ukázkami postavenými na dnes už zacházející knihovně Swing. Tehdy jsem se rozhodl vykročit na nejistou dráhu inovativního spisovatele učebnice o programování a výsledek držíte teď v ruce.
Abych přiblížil učebnicové příklady co možná nejvíce reálným aplikacím, rozhodl jsem se názvy programových konstrukcí (jako jsou třídy objektů, jejich metody či komentáře kódu) ponechat v angličtině. Pro výklad principů webových aplikací bylo třeba zmínit také stručné základy značkovacího jazyka HTML (z anglického Hypertext Markup Language) pro zápis internetových stránek a některé další technologie. Kniha však má také určitý přesah do oblasti návrhu vývoje a životního cyklu informačních systémů. Vzhledem k širšímu záběru témat se kniha nebude zabývat všemi aspekty jazyka do úplných detailů; namísto toho klade větší důraz na vysvětlení ukázek reálného použití, vyjasnění souvislostí a zajímavých vlastností jazyka. Jsem si vědom, že ve snaze udržet dobrou čtivost textu jsem se občas dopustil určitého zjednodušení. Kniha tedy není úplnou specifikací jazyka, spíše se snaží přinést čtenáři nadhled umožňující samostatné získávání potřebných informací z internetových zdrojů. Výklad se záměrně vyhýbá programovým konstrukcím, které sice jsou podle specifikace jazyka platné, ale jejich použití je považováno za nešikovné či nedoporučované. Výchozím zdrojem dalších informací by mohla být dokumentace standardní knihovny jazyka zvaná JavaDoc [5]. Užitečné tipy implementačních postupů lze najít třeba na stránkách serveru StackOverflow [15]. Pokud se podaří touto knihou probudit ve čtenáři zájem o vývoj programových aplikací, pak tahle kniha splní jeden ze svých cílů.
2.2. Co budeme potřebovat?
Kniha předpokládá, že čtenář je běžným uživatelem internetu, zvládne instalaci počítačových programů (například hry nebo internetového prohlížeče), orientuje se v základních pojmech, jako jsou například pevný disk či soubor na disku, a rozumí organizaci ukládaných dat na disku. První kapitola se zaměřuje na motivaci výuky programovacího jazyka Java a zmiňuje také výhody webových aplikací.
Pro lepší pochopení obsahu knihy doporučuji zkoušet si jednotlivé příklady na počítači a následně zkoumat vliv vlastních úprav zdrojového kódu na chování spuštěné aplikace. V případě potíží je vždy možné aktuální projekt nahradit původní verzí jejím rozbalením z archivu.
Co tedy budeme potřebovat pro spuštění ukázkových příkladů?
-
Počítač (či notebook) s volnou kapacitou RAM minimálně 1 GB a 1 GB volného místa na pevném disku s operačním systémem Windows, Linux, MacOS, případně i jiným, na který lze nainstalovat Java Development Kit.
-
Připojení k internetu nejen pro instalaci potřebných programů, ale i k automatickému stažení nezbytných knihoven během spuštění přiložených příkladů.
-
Prostředí Java Development Kit, verze 11 (minimálně 8), pro sestavení a spuštění příkladů.
Knihovna Maven (potřebná pro sestavení a spouštění příkladů) je součástí archivu s příklady. Volba operačního systému bude na vás, hlavními kandidáty na výběr jsou Windows, Linux nebo MacOS. Testování příkladů probíhalo na operačním systému Xubuntu [17], a nelze vyloučit, že u jiných operačních systémů budou nároky vyšší. Systém Xubuntu je lehká linuxová distribuce s grafickým prostředím Xfce, podporovaná firmou Canonical, která vychází z distribuce Ubuntu založené na Debian GNU/Linux. Instalace Javy a dalších nezbytných knihoven ukrojí z pevného disku asi 500 MB.
2.3. Proč se učit programovat?
Pokud se vám dostala tato kniha do ruky, zřejmě už jste nějakou motivaci našli. Pracovní trh se potýká s nedostatkem odborníků, a to nejen v oboru informačních technologií (dále jen IT). V mnoha oborech může být řešením nasazení automatizace či umělé inteligence, nic z toho se však bez vývojářů softwaru (SW) neobejde, což ještě více posílí poptávku po IT odbornících. Běžným obchodníkům pomůže někdy i jednoduchá prezentace zboží (či služeb) na internetu s možností sestavení objednávky a následné platby.
Je dobré vědět, že zadání projektu i výsledek práce SW vývojáře se sdílí relativně snadno přes internet, a tak pokud někdo má dostatečné jazykové znalosti, a zároveň získá důvěru zákazníka, může dodávat svoji práci třeba na druhý konec zeměkoule z pohodlí svého domova.
2.4. Proč se učit právě jazyk Java?
Říká se s nadsázkou: „Kolik právníků, tolik názorů.“ U programovacích jazyků je to podobné: diskuze o výhodách různých programovacích jazyků bývají velmi emotivní, přitom každý argument může mít svůj díl pravdy. V této nepřehledné situaci může být pro začátečníka obtížné vybrat správný směr. Proč tedy právě Java?
-
Programovací jazyk Java patří na trhu práce dlouhodobě k nejžádanějším, je tedy pravděpodobné, že čas strávený studiem se podaří brzy zhodnotit [18].
-
Hotové aplikace lze spouštět (při dodržení určitých pravidel) na různých operačních systémech (například: Windows, Linux, MacOS), pokud je na nich k dispozici běhové prostředí zvané Java Runtime Environment (dále jen JRE). Takové schopnosti programovacího jazyka se označují výrazem multiplatformní.
-
Nástroje nezbytné pro vývoj a provoz standardních komerčních aplikací lze získat zdarma, včetně sofistikovaných editorů zaměřených na vývoj aplikací (dále jen IDE), podporujících i sestavení projektu a jeho spuštění. Referenční implementace Javy s otevřenou licencí má název OpenJDK [4], existují však implementace i jiných dodavatelů.
-
Java patří mezi jazyky s mimořádně rozsáhlou nabídkou různých knihoven s otevřeným zdrojovým kódem (anglicky open‑source), které mohou vývojářům ušetřit drahý čas či zbytečnou práci.
-
Kolem jazyka se vytvořila silná komunita, která přispívá ke včasnému odhalení případných problémů.
-
Vývojáři jazyka Java dosud věnovali značnou pozornost zpětné kompatibilitě jazyka při vydávání jeho nových verzí. V praxi to znamená, že uživatel má velkou naději, že spustí starší Java projekt i na poslední verzi prostředí (anglicky run-time), a vývojáři zase mohou používat (kompilovat) starý zdrojový kód bez úprav v poslední verzi překladače.
-
Jazyk nabízí silnou typovou kontrolu, která urychluje odhalení některých chyb zápisu zdrojového kódu, a tím šetří čas potřebný na testování aplikace.
-
Java se řadí k programovacím jazykům, které za běhu aplikace uvolňují automaticky paměť po nepotřebných objektech, což urychluje vývoj a eliminuje některé problémy s vyčerpáním paměti. Uvolňování paměti má na starost algoritmus označovaný anglickým termínem Garbage collector. V určitých situacích (například během řízení časově kritických procesů) však může krátké časové prodlení spojené s uvolněním paměti představovat handicap, který však lze eliminovat výběrem vhodné strategie algoritmu Garbage collector.
-
Programovací jazyk Java je k dispozici vývojářům relativně dlouhou dobu a dnes už je standardem pro výuku objektového programování (OOP). Znalost principů objektového programování jazyka Java může usnadnit pochopení objektového přístupu také v jiných jazycích.
2.5. Proč webové aplikace?
Webové aplikace pro internetové prohlížeče se těší stále větší oblibě koncových uživatelů i provozovatelů a důvodů je hned několik.
-
Internetové připojení je dnes už snadno dostupné pro většinu uživatelů osobních počítačů i mobilních zařízení.
-
Nové internetové technologie umožňují vývojářům stále zlepšovat ergonomii webového uživatelského rozhraní, které se pomalu blíží komfortu desktopových aplikací.
-
Webové aplikace jsou na operačním systému prakticky nezávislé, klienti tedy mohou používat libovolné zařízení, které poskytuje internetový prohlížeč.
-
Distribuce nových verzí SW produktů k zákazníkovi má minimální náklady.
-
Pro útočníka je obtížné webovou aplikaci odcizit, nebo prolomit její licenční omezení.
2.6. Pár slov o jazyku Java
Java je objektově orientovaný programovací jazyk se silnou typovou kontrolou. Objektové programování je specifický způsob zápisu zdrojového kódu do textových souborů popisujících třídy, které mají svůj název, datovou složku a skupinu algoritmů, jež jsou obsaženy v pojmenovaných útvarech zvaných metody. Více informací k metodám třídy najdeme v kapitole Metody. Mezi důležité vlastnosti OOP patří dědičnost tříd, která umožňuje efektivně deklarovat společné vlastnosti a chování objektů – podrobnosti zmíníme v kapitole Dědičnost, předtím si však vysvětlíme některé úvodní pojmy.
Zdrojový kód jazyka Java se zapisuje do textových souborů (s příponou .java
) podle pravidel, kterým se říká specifikace jazyka [3].
Jak se programovací jazyk vyvíjí, tak vznikají nové verze specifikace, které korespondují s určitým Java prostředím (instalovaným na počítači).
Staré programy zpravidla fungují i v novějších prostředích Javy, této vlastnosti se říká zpětná kompatibilita.
Příklady ke knize byly odladěny ve verzích Java 8 a 12, pravděpodobně je však bude možné používat (bez dalších úprav) i na verzích vyšších.
Z toho důvodu je i výklad postaven tak, aby pokrýval uvedený rozsah verzí jazyka.
Verze 8 má dlouhodobou podporu plánovanou až do roku 2030.
Hodí se doplnit, že před verzí 9 se hodnoty uváděly jedničkou, a tak zápis 1.8 lze považovat za alternativu verze 8.
Zdrojové soubory se ukládají do adresářové struktury projektu (ve Windows se souborovým adresářům říká složka).
Stromová struktura projektu není úplně libovolná, ale má svá pravidla – některá vycházejí ze specifikace jazyka a jiná jsou daná strukturou projektu typu Maven [7].
V hlavním (nejvyšším) adresáři projektu Maven je umístěn textový soubor s názvem pom.xml
.
Tento soubor obsahuje formální informace o projektu (název, verzi), ale i řadu informací jiných, jako je třeba závislost na dalších knihovnách či požadovaná verze Javy.
Může obsahovat řadu dalších informací užitečných pro sestavení projektu. Nutno dodat, že Maven projekt se může skládat z několika menších částí, kterým se říká moduly, přitom každý modul má podobnou strukturu včetně svého vlastního souboru pom.xml
.
Při instalaci Javy se setkáme se dvěma typu produktů:
-
Java Runtime Environment (JRE) je běhové prostředí, které potřebuje koncový uživatel programu ke spuštění aplikace vytvořené v jazyce Java. Tohle však není případ uživatelů služeb využívaných přes webový prohlížeč, protože uživatelé takových aplikací pracují (prostřednictvím svého internetového prohlížeče) jen s texty, které byly pomocí jazyka Java sestaveny, a pro prezentaci takových textů prohlížeč Javu nepotřebuje.
-
Java Development Kit (JDK) je nástroj pro vývojáře, který poskytuje kompilátor zdrojových kódů a některé další služby užitečné pro vývoj a ladění aplikace. JRE je automaticky obsaženo v JDK, pro vývoj aplikací tedy stačí instalovat pouze produkt JDK.
Nezaměňujme programovací jazyk Java se skriptovacím jazykem JavaScript, protože se jedná se o dva různé jazyky. Zdrojový kód JavaScriptu se provádí (interpretuje) bez předchozí kompilace, což neumožňuje zachytit včas překlepy a typové nekonzistence, interpretace skriptu bývá pomalejší. Výhodou JavaScriptu je snadná přenositelnost, na straně internetového prohlížeče se využívá k úpravě webových stránek (či sestavení celých částí), v našich příkladech však tento jazyk využívat nebudeme. |
Kompilátor v jazyce Java je program, jenž prověří správnou syntaxi (soubor pravidel) zápisu zdrojového kódu a převede ho do binární podoby (zvané bytecode), se kterou umí efektivně pracovat prostředí JRE.
Kompilované soubory mají příponu class
.
Standardní kompilátor JDK se liší od některých jiných kompilátorů (i jiných jazyků), které převádějí zdrojový kód do binárního formátu, jenž obsahuje přímé instrukce reálného procesoru počítače. Pro spuštění běžné Java aplikace však často nevystačíme s pouhou kompilací, protože nejdříve potřebujeme stáhnout z internetu potřebné knihovny, a pak budeme chtít projekt také nějak spustit. Takové komplexní služby nám zajistí aplikace Maven.
3. Základní pojmy
Pokud chcete problém vyřešit, musíte jej nejdříve pojmenovat, a řešit příčinu, ne příznaky.
český vědec a vynálezce
Většina problémů, se kterými se (nejen) začínající programátoři setkávají, má své řešení někde na internetu, stačí ho najít. Klíčem k nalezení řešení bývá správná formulace vyhledávacího dotazu, ale ten se bez správných pojmů sestavuje jen obtížně.
3.1. Data
V oblasti výpočetní techniky jsou data libovolné informace vhodné k počítačovému zpracování. Nejmenší jednotkou dat je bit, který obsahuje pouze dva stavy: (ano/ne), nebo (pravda/nepravda), nebo třeba (přítomen/nepřítomen), interpretace mohou být různé. Protože stav lze vyjádřit také pouhou číslicí (1/0), tak různá zařízení (stroje, přístroje), která interně umí pracovat s čísly, se nazývají občas digitální zařízení (číslicové stroje, přístroje). Skládáním bitů do větších celků vznikají datové útvary, jejichž hodnota opět závisí na interpretaci. Pro vyjádření větší velikosti datového objemu se běžně používá jednotka bajt (anglicky byte) obsahující 8 bitů. U těchto jednotek můžeme použít předpony soustavy SI (Mezinárodní systém jednotek) podle vzoru:
-
kilobajt – označuje tisíc bajtů, zkratka je kB,
-
megabajt – označuje milion bajtů, zkratka je MB,
-
gigabajt – označuje miliardu bajtů, zkratka je GB.
3.2. Algoritmus
Výklad pojmu algoritmus začneme jednoduchou definicí:
Algoritmus je návod či postup, kterým lze vyřešit daný typ úlohy. Přepis algoritmu do programovacího jazyka se nazývá programování.
Jistou podobnost k algoritmu lze najít třeba v kuchařském receptu na přípravu pokrmu. Vezměme si například přípravu míchaných vajec pro dvě osoby.

Postup přípravy by mohl vypadat takto:
-
Připravíme si suroviny: 4 vejce, šunku, olej, sůl.
-
Na pánvi ohřejeme olej na 160 °C.
-
Přidáme šunku nakrájenou na kostičky.
-
Přidáme vejce.
-
Mícháme po dobu 3 minut.
-
Pokrm podáváme na talíři teplý a osolený.
Tento recept jistě není zcela přesný, ale zkušená kuchařka ví, že vejce je třeba zbavit nejdříve skořápek a sporák je nutné nakonec vypnout; stroj to však neví. Z příkladu je také zřejmé, že pro srozumitelný popis přípravy pokrmu se neobejdeme bez jisté míry abstrakce. Hned první krok by se dal rozepsat samostatným algoritmem:
-
Vyhledáme v lednici potraviny na přípravu míchaných vajec.
-
Pokud některá z potravin chybí (kontrola obsahu lednice), pošleme člena domácnosti na nákup chybějících surovin.
-
Požadované suroviny odneseme na kuchyňskou linku.
Také proces nakupování by se dal nepochybně popsat samostatným algoritmem a tímto způsobem bychom mohli pokračovat směrem ke stále podrobnějším detailům. Pojem kroky algoritmu lze chápat jako příkazy, které v jazyce Java realizujeme voláním metod. Každý příkaz přebírá odpovědnost za nějakou dílčí část algoritmu. Na našem příkladu vidíme, že součástí algoritmu mohou být také příkazy zpracované za určité podmínky (pokud nějaká potravina chybí, běž nakoupit).
Zapamatujme si, že příkazy se provádějí běžně shora dolů (nebo zleva doprava, pokud je více příkazů na jednom řádku) a každý příkaz se provádí běžně až po dokončení příkazu předchozího. Pro tuto chvíli pomiňme příkazy jazyka pro iterace a paralelní zpracování. |
Pokud pošleme člena rodiny na nákup, s dalším postupem přípravy pokrmu pokračujeme až po jeho návratu. V běžném životě však zpravidla nečekáme v pozoru, až se někdo vrátí, ale třeba nachystáme jídelnu. Čtyřjádrový procesor si můžeme představit jako rodinu se čtyřmi členy a zpracování algoritmů několika jádry procesoru se nazývá vícevláknové zpracování. Toto téma však přesahuje plánovaný rámec knihy, a tak budeme dál uvažovat o všech algoritmech pouze v režii jediného jádra procesoru. Dopad na praktický život by to mělo ten, že nákup chybějících potravin zajistí kuchař sám.
Jak už jsme naznačili před chvílí, algoritmy se v jazyce Java zapisují (v textovém formátu) do útvarů zvaných metody. Každá metoda má svůj název, může vyžadovat seznam parametrů a může vracet návratovou hodnotu, kterou může být také objekt. Parametry umožňují předávání dat do metody z místa, kde je metoda volána svým jménem. Nákup potravin by vedl k návrhu metody s těmito vlastnostmi:
-
název metody: „běž nakoupit“
-
parametr metody: „seznam potravin“
-
návratový objekt: „nákupní taška se zakoupenými potravinami“
Z pohledu kuchařky je popis algoritmu nákupu nezajímavý, kuchařce stačí znát jen způsob volání potřebné metody, která chybějící suroviny zajistí.
Zapamatujme si, že při počítačovém zpracování se využívají algoritmy (například recept na přípravu pokrmů) a data (nákupní seznam, potraviny, přepravní taška). |
3.3. Proměnná
Pojem proměnná označuje symbolické jméno pro úschovu informace za běhu programu. Proměnnou si můžeme pro začátek představit jako krabici na úschovu předmětů: pro uložení sezonních bot lze použít krabici na boty opatřenou štítkem „Petrovy zimní boty“. V případě potřeby lze krabici podle štítku snadno vyhledat a obsah použít, nebo tam uložit boty nové, o původní obsah tím však přijdeme. Přitom je jasné, že do krabice s Petrovými botami se už nevejdou boty od tchyně, a pro úschovu toaletního mýdla si zvolíme raději krabičku menší. K té podobnosti s krabicí mají nejblíže primitivní datové typy, které si vysvětlíme v následující kapitole.
V tvorbě programů používáme proměnné k úschově dat pro pozdější použití.
Využití proměnné má jen omezenou platnost v rámci nějakého programového bloku, který se vyznačuje ve zdrojovém kódu složenými závorkami {
a }
.
3.4. Primitivní datové typy
V jazyce Java rozlišujeme tři základní skupiny datových typů:
-
primitivní typy,
-
třídy objektů a
-
pole.
V jazyce Java má každá proměnná svůj pevně daný datový typ, který se uvádí při její deklaraci, a doplňme, že od verze Java 10 existuje ještě deklarace typu proměnné prvním přiřazením hodnoty.
Pod pojmem deklarace si lze tady představit nějaké prohlášení ve zdrojovém kódu.
Tahle kapitola se zabývá popisem primitivních datových typů, k těm ostatním se dostaneme postupně.
Primitivní typy obsahují číslo z vymezeného intervalu, případně jeho speciální interpretaci, jako je znak nebo logická hodnota.
Rozsah povolených hodnot primitivního datového typu je součástí specifikace jazyka a jejich počet je omezen – na rozdíl od tříd, které si můžeme vytvářet prakticky v neomezeném množství.
Java nabízí celkem osm primitivních typů, jejich názvy začínají vždy malým písmenem.
Slovo literál ve zdrojovém kódu označuje zápis konkrétní hodnoty nějakého konkrétního typu.
Například celočíselný literál s hodnotou deset lze ve zdrojovém kódu zapsat výrazem 10
, literál desetinného čísla lze zapsat podle vzoru: 65.731F
;
Literály uvedené v následující tabulce však nejsou jedinou platnou formou zápisu, více informací je třeba tady [19].
Číselné limity zapsané (v tabulce) v exponenciálním tvaru (neboli vědeckou notací) byly zaokrouhleny.
Za běhu programu lze získat přesné limity prostřednictvím objektových ekvivalentů, praktické použití si ukážeme za chvíli.
# | Typ | Literál/ Konstanta | Rozsah | Nejmenší hodnota | Největší hodnota | Objektový ekvivalent |
---|---|---|---|---|---|---|
1. |
|
|
1 bit |
false |
true |
|
2. |
|
|
8 bitů |
-128 |
127 |
|
3. |
|
|
2 bajty |
-32 768 |
32 767 |
|
4. |
|
|
2 bajty |
0 |
65 536 |
|
5. |
|
|
4 bajty |
-2.1e9 |
2.1e9 |
|
6. |
|
|
8 bajtů |
-9.2e18 |
9.2e18 |
|
7. |
|
|
4 bajty |
-3.4e38 |
3.4e38 |
|
8. |
|
|
8 bajtů |
-1.8e308 |
1.8e308 |
|
Popis hodnot
-
boolean
– logická hodnota; může obsahovat jen dva stavy:true
afalse
, -
byte
– nejmenší číselný typ (česky bajt) obsáhne znakovou sadu ASCII s 256 znaky, do této sady se však nevejdou všechny národní znaky ani piktogramy. Velikost datové informace se uvádí často jako násobek tohoto datového typu. Literál se vytváří přetypováním, což je změna datového typu vyznačená v kulatých závorkách. Příklad použití je vidět v tabulce, podrobnější informace k tomu tématu jsou uvedeny v kapitole o přetypování, -
short
– krátké celé číslo. Datový typ nemá vlastní literál, hodnotu tohoto typu lze vytvořit také přetypováním, -
char
– kladné celé číslo, které svým rozsahem pokrývá znakovou sadu Unicode [11], kde pojem znaková sada představuje číslovaný seznam grafických znaků (či symbolů), Každá číselná hodnota tohoto typu je při zobrazování prezentována odpovídajícím grafickým znakem z této sady. Pro lepší představu je možné nahlédnout na řešený příklad z kapitoly o sestavování textu, -
int
– nejčastěji využívané celé číslo v jazyce Java. Hraniční hodnoty jsou jen orientační, přesné hodnoty jsou třeba zde [19]. Hodnota se uvádí jako číslo bez desetinné části. Číslice primitivních typů lze (ve zdrojovém kódu) prokládat znakem podtržítka (_
) pro lepší čitelnost, na hodnotu čísla to však nemá vliv, -
long
– velké celé číslo, jeho číselná hodnota se doplňuje znakemL
, přípustný je i malý znak, který se však kvůli podobnosti s jedničkou moc nepoužívá, -
float
– typ čísla s (plovoucí) desetinnou čárkou, jeho hodnota se doplňuje znakemF
, přípustný je i malý znak. Přesnost obsaženého čísla je omezená (svým typem), a tak při výpočtu může docházet k zaokrouhlení, -
double
– typ velkého čísla s (plovoucí) desetinnou čárkou, jeho hodnotu lze doplnit (volitelně) znakemD
, přípustný je i malý znak. Přesnost obsaženého čísla je omezená (svým typem), a tak při výpočtu může docházet k zaokrouhlení.
3.4.1. Java výrazy a příkazy
Pod pojmem programový výraz si lze představit (matematický) vzorec, kde dosazením hodnot získáme nějaký výsledek.
Přesněji bychom mohli říci, že programový výraz ve zdrojovém kódu označuje literál, proměnnou, volání metody, nebo jejich kombinaci (spojovanou operátory) v souladu se syntaxí jazyka tak, aby jejich vyhodnocením za běhu programu vznikla hodnota.
Pokud hodnotu výrazu přiřadíme do proměnné, vytvoříme příkaz, který se zakončuje středníkem (;
).
Zápis hodnoty do proměnné se skládá ze dvou částí oddělených rovnítkem (=
).
Levá strana obsahuje název proměnné, do které chceme výsledek zapsat, při prvním použití se název proměnné uvádí datovým typem.
Pravá část popisuje programový výraz odpovídajícího typu.
Vedle přiřazovacích příkazů rozeznáváme ještě příkazy typu volání metod a příkazy jazyka.
Zapamatujme si, že pokud je programový kód zakončen středníkem (v těle metody), jedná se o příkaz, a v tom případě hodnota následujícího sloupce ukazuje obsah související proměnné. |
Ve sloupci Kód následující tabulky najdeme vybrané ukázky výrazů a příkazů, ukázky bez středníku reprezentují výraz. Zalamování řádků (na pozici mezer) je pro kompilaci nevýznamné.
# | Kód | Výsl. | Typ | Poznámka |
---|---|---|---|---|
1. |
|
true |
boolean |
Příkaz, který přiřadí hodnotu logického literálu do nové proměnné |
2. |
|
A |
char |
Zápis znaku |
3. |
|
5 |
int |
Zápis číselné hodnoty |
4. |
|
500 |
int |
Do proměnné |
5. |
|
261 |
int |
Do proměnné |
6. |
|
false |
boolean |
Do logické proměnné |
7. |
|
1261 |
int |
Tento zápis změní hodnotu proměnné |
8. |
|
2261 |
int |
Uvedený výraz představuje stručnější zápis toho předchozího, do naší hotovosti přičte dalších |
9. |
|
true |
boolean |
Nová kontrola hotovosti pro nákup zboží, výsledek změní původní hodnotu proměnné |
10. |
|
1761 |
int |
Po zaplacení zboží zbude v proměnné |
11. |
|
true |
boolean |
Příklad srovnání aktuální hotovosti s maximální hodnotou typu |
12. |
|
false |
boolean |
Pro porovnání shody primitivních datových typů se používá operátor |
13. |
|
3 |
int |
Výraz funguje jako funkce, která vrátí větší ze dvou hodnot uvedených v kulatých závorkách. Výrazům tohoto typu se budeme věnovat podrobněji dále. |
14. |
|
true |
boolean |
Výraz porovnává výsledek dvou matematických operací, výsledkem je logická hodnota. Aplikování matematického operátoru má přednost před operátorem porovnání, proto zde nejsou třeba závorky. |
15. |
|
false |
boolean |
Výraz pro porovnání dvou číselných výrazů. |
16. |
|
true |
boolean |
Logický součet se vyznačuje párem svislých znaků (anglicky pipe). |
17. |
|
false |
boolean |
Logický součin se vyznačuje párem znaků ampersand (anglicky ampersand). |
18. |
|
true |
boolean |
Příklad negace logického výrazu |
Výše uvedené příklady uvádějí jen vybraný seznam operátorů a jejich možných kombinací, další příklady najdeme v kapitole Programové výrazy a podrobnější výklad tématu třeba zde [22].
Výhodou primitivních typů je nízká paměťová náročnost a podpora operátorů
(výrazů pro matematické a logické operace).
3.4.2. Přetypování a autoboxing
Připomeňme si, že typová konverze (zkráceně přetypování) je přeměna jednoho datového typu na druhý. V případech, kdy hrozí riziko ztráty informace původní hodnoty, je třeba použít přetypování explicitní (pomocí kulatých závorek), jinak lze hodnoty pořizovat přímo.
I když se objektům začneme věnovat až později, zmíníme už nyní, že každý primitivní datový typ má v jazyce Java svůj objektový ekvivalent, jeho název popisuje poslední sloupec v tabulce primitivních typů. Na rozdíl od primitivních typů se názvy objektových typů zapisují s velkým počátečním písmenem. Díky vlastnosti jazyka zvané autoboxing je možné ve zdrojovém kódu zapisovat hodnoty (výrazy) primitivních typů do typů objektových a naopak, potřebnou konverzi za nás doplní kompilátor jazyka Java automaticky. Autoboxing funguje i směrem opačným. Použití si ukážeme na několika jednoduchých příkladech.
# | Kód | Výsl. | Typ | Poznámka |
---|---|---|---|---|
1. |
|
5 |
Integer |
Konverze z primitivního typu na objektový pomocí vlastnosti autoboxing. |
2. |
|
A |
Character |
Vytvoření objektu, který reprezentuje znak tabulky Unicode. |
3. |
|
true |
Boolean |
Vytvoření objektu, který reprezentuje logickou hodnotu. |
4. |
|
5 |
int |
Příklad zápisu objektového typu do primitivního. |
Objektové ekvivalenty primitivních datových typů jsou neměnné objekty (anglicky immutable objects), což v důsledku znamená, že objekt reprezentující číslo 1
už nemůže (později) reprezentovat číslo 2
.
Souvislosti budou jasnější, až se dostaneme k atributům objektu.
3.5. Texty v jazyce Java
Vedle objektových ekvivalentů (k těm primitivním typům) se budeme setkávat často ještě s jedním zajímavým objektovým typem, který může reprezentovat nějaké slovo či větu.
Podrobněji se tomu tématu budeme věnovat později, pro začátek nám stačí vědět, že textové literály se ve zdrojovém kódu ohraničují dvojitými uvozovkami, lze je spojovat operátorem +
a přiřazovat do proměnných typu String
.
String
String name = "Joe"; (1)
name = name + " Black"; (2)
1 | Text ohraničený dvojitými uvozovkami reprezentuje ve zdrojovém kódu objekt typu String , který můžeme přiřadit do proměnné – podobně, jako tomu bylo u primitivních datových typů. |
2 | Dva objekty typu String lze spojit operátorem + , výsledkem pak bude nový objekt obsahující text Joe Black , který zapíšeme opět do proměnné name , čímž nahradíme její původní obsah. |
Z ukázky je zřejmá jistá podobnost s primitivními datovými typy. Podrobněji se budeme tímto typem zabývat v kapitole Třída String, kde už budeme znát některé pojmy objektového programování.
3.6. Stromová struktura dat
Data lze seskupovat do různých struktur, které mohou urychlit vyhledávání dat, nebo zjednodušit manipulaci. Se stromovou strukturou se budeme setkávat poměrně často, hodí se tedy uvést včas základní pojmy.
Pro lepší představu o tomto datovém modelu si vezměme na pomoc strom jabloně s jejími listy a plody. Místo, kde se větve dělí, se nazývá uzel (anglicky node), pojmem uzel se však označuje obecně každé místo připojující další potomky (na obrázku jabloně to jsou: další větve, plody či listy stromu). Koncový uzel, který nepřipojuje další potomky, se označuje termínem list (anglicky leaf). Každý uzel pak má jednoho svého rodiče – s výjimkou prvního uzlu na kmeni stromu, kterému se říká kořen (anglicky root).

Každému uzlu lze přiřadit název a pojmenovat můžeme i všechny listy. Pojem list se někdy nahrazuje výrazem koncový uzel a spojnice mezi uzly se nazývá hrana. Pokud potřebujeme vyjádřit přesnou adresu určitého uzlu, stačí nám zapsat postupně všechny názvy uzlů směrem od kořene stromu, oddělovat je můžeme třeba tečkou.
a.e.f.h.žlutý_list
Podobně bychom mohli lokalizovat i červené jablko vpravo nahoře.
a.e.i.k.m.jablko
Všimněte si, že stromovou strukturu vykazuje také adresa na poštovní obálce, kde kořen stromu může být jméno státu a oddělovačem uzlů je nový řádek. Dalším příkladem může být hierarchická klasifikace organismů (s dělením na říše, kmeny, třídy a další úrovně), podobných příkladů by se našla jistě celá řada.
3.7. Model reálného světa
Vysvětlení pojmu model uvedeme krátkou definicí:
Model je zjednodušená reprezentace určitého objektu reálného světa či systému – pojatá z určitého úhlu pohledu.
Podívejme se do světa malých dětí, některé si hrávají s modely autíček, jiné s panenkami. Oba typy hraček lze považovat za zjednodušenou napodobeninu předmětu reálného světa. Při výrobě hraček se často preferuje zachování tvaru předlohy, barev, někdy zvuků. Jiné vlastnosti jsou naopak potlačeny v zájmu bezpečí uživatele nebo finanční dostupnosti.
K obecnému pojmenování takových zjednodušených napodobenin můžeme použít slovo model. Pokud se model skládá z informací vhodných pro počítačové zpracování, říkáme mu datový model.
3.8. Objekt reálného světa a těleso
Význam slova objekt v běžné řeči je poměrně obecný a závisí na kontextu. Pro jeho lepší pochopení v prostředí objektově orientovaného jazyka nám může pomoci jistá podobnost s výrazem těleso, který se používá ve fyzice pro popis fyzikálních zákonů. Slovem těleso se označuje hmotný předmět, který má své místo (lokalitu) a velikost v prostoru a také čas vzniku i zániku, přitom není podstatné, zdali pozorovatel zná všechny tyto údaje. Za těleso lze považovat libovolný konkrétní předmět (dům, kedlubna, veverka), mezi tělesa však nepatří abstraktní pojmy (chůze, spánek, fyzikální jednotka).
Je užitečné si připomenout, že tělesa mají své vlastnosti (např. hmotnost, barvu, teplotu) a mohou poskytovat také služby, např. automat na jízdenky může (za určitých podmínek) vytisknout cestovní lístek. Pojem těleso pokrývá jen podmnožinu objektů reálného světa, ale kvůli některým podobnostem s počítačovými objekty bude užitečné zmínit ho za chvíli.
3.9. Programový objekt
Programový objekt (v našem případě Java objekt) je model uložený v operační paměti počítače, který popisuje reálné či abstraktní objekty reálného světa pomocí dat a algoritmů. Jeden objekt může reprezentovat konkrétní automobil, vybraný umělecký styl nebo zelenou barvu. Co to má společného s tělesem? Počítačový objekt má také své místo v prostoru (jednoznačnou adresu v operační paměti počítače), velikost (objekt zabírá jasně vymezenou část paměti) a čas svého vzniku i zániku. Java objekty vznikají příkazem a zanikají při automatickém sběru nepotřebných objektů, jak už bylo zmíněno na začátku knihy. Všechny objekty zaniknou nejpozději při vypnutí počítače, z pohledu definice už není podstatné, že existují techniky pro ukládání objektů na pevný disk i pro jejich případnou obnovu v paměti po zapnutí počítače. Programové objekty (podobně jako tělesa) mají také své vlastnosti a mohou nabízet služby, jako třeba výpočty či vyhledávání dat.
Programový objekt představuje základní stavební kámen pro budování komplexního datového modelu reálného světa. |
3.10. Třída objektů
Pokud mají dva objekty společné vlastnosti, můžeme říkat, že jsou stejného typu. V reálném světě se můžeme podívat na parkoviště velkých měst, která jsou někdy plná objektů sloužících k osobní dopravě. Většina z nich bude mít svůj motor, kola, volant, dveře… a takovým objektům říkáme obecně automobily. Na parkovišti však můžeme najít také jednostopá vozidla s motorem, která nemají žádné dveře a místo volantu mají řídítka, takovým objektům říkáme motocykly. Co mají auta a motorky společného? Podle silničního zákona patří do skupiny motorových vozidel.
Třída objektů (dále jen třída) je technický pojem, který označuje datový typ objektu.
Každá třída nabízí služby (prostřednictvím svých metod) a definuje vlastnosti, které objekt může nabývat za běhu programu.
Požadavek na vznik nového objektu se zapisuje ve zdrojovém kódu odkazem na název třídy, a z toho důvodu se někdy používá pojem instance třídy, což není nic jiného než objekt.
Jaký je mezi těmi pojmy rozdíl?
Pokusme se to vyjasnit na třídě s názvem Car
, která slouží pro datový model automobilu (anglicky car).
Pokud získáme instanci třídy Car
, můžeme také říci, že máme k dispozici objekt s metodami a vlastnostmi třídy Car
.
Pokud však získáme objekt typu Car
, nemusí se jednat nutně o instanci třídy Car
, protože se může jednat také o jejího potomka.
Třídy mají totiž hierarchickou strukturu, více informací zmíníme v kapitole o dědičnosti tříd.
Zapamatujme si, že třídy definují vlastnosti a metody, které se vyjadřují prostředky jazyka ve zdrojovém kódu třídy. Použitím tříd vznikají za běhu programu objekty. |
3.10.1. Diagram modelu tříd
Na realizaci programových (anglicky: software, dále jen SW) projektů se podílí celá řada lidí, kteří zastávají role od managerů, obchodníků, analytiků přes vývojáře, testery, administrátory až po konzultanty. Spuštění vývoje projektu závisí na řadě okolností a nezřídka začíná u obchodníků, kteří zprostředkují odhad výsledné ceny zakázky. Pokud cenu zákazník akceptuje, vývoj projektu projde postupně několika fázemi, které se označují souhrnným termínem životní cyklus projektu. Stručný popis těchto fází vypadá takto:
-
Sběr a specifikace požadavků zákazníka.
-
Analýza a návrh řešení.
-
Implementace návrhu.
-
Testování produktu.
-
Nasazení produktu u zákazníka a jeho další údržba.
Přiřazení rolí projektu konkrétním lidem nemusí být vždy zcela jasně ohraničené a někdy jeden člověk zastává rolí více. Aby mohl tým efektivně spolupracovat, bývá užitečné vytvořit skupinu dokumentů – od soupisu požadavků zákazníka přes návrh řešení, testování a uživatelskou dokumentaci až po vymezení rozsahu technické podpory a záruk. Jedním z důležitých dokumentů této skupiny je analytický dokument, který obsahuje návrh tříd a jejich vzájemných vztahů. K jejich znázornění se využívají běžně prostředky jazyka UML (Unified Modeling Language) s několika typy diagramů. My si ukážeme jeden z nich, který se nazývá diagram tříd (anglicky class diagram).
Diagram tříd je vyjádření statické struktury datových typů grafickými prostředky. V diagramu jsou vyjádřeny jednotlivé třídy (včetně atributů a metod) a jejich vzájemné vztahy. |
Představme si na chvíli situaci, že dostaneme za úkol vybudovat elektronickou evidenci vozidel, a podívejme se, jak by mohl vypadat první návrh třídy pro popis automobilu.
Na obrázku vidíme barevný obdélník, který je dvěma linkami rozdělený na tři části. Barva obdélníku má jen estetický význam a běžně se používá také barva bílá. Podívejme se na popis atributů této třídy:
-
modelName
– název modelu auta (například: Auris), -
enginePower
– výkon motoru v kilowattech, -
manufacturer
– výrobce vozidla (například: Toyota), -
made
– datum vyrobení vozu, -
motorSerialNumber
– výrobní číslo motoru, -
trunkVolume
– objem zavazadlového prostoru v litrech, -
owner
– poslední majitel vozu.
Návrh třídy Car
sice není úplně dokonalý, ale zatím nám dobře poslouží k vysvětlování pojmů, a vylepšovat ho budeme později.
Diagramy tříd mohou mít zakreslenou různou míru podrobnosti. Seznam atributů je nepovinný, podobně jako výčet metod. Datové typy atributů a metod (vstupních a výstupních dat) jsou rovněž nepovinné. Diagram pak může obsahovat v minimalistické verzi pouze název třídy. |
3.10.2. Název třídy
Horní část diagramu obsahuje název třídy.
Názvy metod se skládají z malých a velkých písmen, cifer a několika dalších znaků, přitom název nesmí začínat cifrou, nemůže obsahovat znaky operátorů (matematických, logických), a nemůže tedy obsahovat mezery a jiné bílé znaky (do této skupiny patří i tabulátor a znak nového řádku).
Názvy se nesmí shodovat s rezervovanými slovy jazyka Java [26] (jinak také klíčová slova, anglicky keywords), přitom velká a malá písmena jsou (pro odlišení názvu) významná, což znamená, že například název třídy Company
se liší od názvu ComPany
.
Stejná pravidla platí také pro názvy atributů, metod a proměnných.
V jazyce Java se doporučuje názvy tříd uvádět velkým písmenem, pokud je název složený z více slov, použije se notace zvaná CamelCase (česky velbloudí notace), kde jsou jednotlivá slova oddělena velkým písmenem.
3.10.3. Atributy
Programový objekt má své vlastnosti zvané atributy, které jsou deklarované především svým datovým typem a názvem (k viditelnosti se dostaneme později). Výčet atributů ve třídě je konečný (anglicky se nazývají: attributes, properties) a v diagramu se zakresluje do jeho prostřední části, datový typ každého z atributů lze volitelně uvést za jeho názvem odděleným dvojtečkou.
Schopnost objektu obsahovat jiné objekty se nazývá kompozice (anglicky composition) a je důležitou vlastností objektově orientovaného programování. |
3.10.4. Metody
Metody jsou pojmenované části třídy, které obsahují algoritmy vyjádřené v programovacím jazyce.
Použití metody se nazývá volání metody a zapisuje se jménem následovaným závorkami s případnými parametry metody.
Zápis volání metody tak může připomínat zápis matematické funkce (například: y=sinus(x)
) s dodatkem, že lze vytvářet také metody bez parametrů i bez návratových hodnot (příkladem může být třeba přechod tiskové hlavy na nový řádek dle vzoru: printNewLine()
.
Jedna třída může obsahovat více metod stejného jména, pokud se budou jejich parametry lišit počtem či typem.
V takových případech hovoříme o přetížených metodách.
Výčet metod se uvádí ve spodní části obdélníku. Každý název metody reprezentuje určitou službu, přičemž tato metoda může dostávat vstupní parametry (argumenty metody), a může vracet právě jednu hodnotu (určitého typu). Smysl mohou mít i metody, které nedostanou žádný parametr, nebo nevracejí žádnou hodnotu. V těle metody lze pracovat jak s atributy objektu, tak i s parametry metody, a samozřejmě také s lokálními proměnnými. V případě práce s objekty lze volat jejich metody, nebo vytvářet objekty nové. Dokumentace uvádí především to, co má metoda dělat, konkrétní způsob implementace se často považuje za detail. Implementace metody však může mít zásadní vliv na rychlost provedení či paměťovou náročnost.
Obě spodní části diagramu jsou nepovinné a je možné uvést jen název třídy pro nějaký abstraktní návrh, v tom případě se oddělovače nezakreslují.
Při návrhu tříd se doporučuje oddělovat třídy s datovým obsahem od tříd, kde dominují algoritmy. Té první skupině se pak říká datové třídy a druhá skupina se nazývá servisní třídy. |
3.10.5. Konstruktor
V předchozím odstavci jsme zmínili, že třída má své jméno a může mít i své vlastnosti a metody.
Třída má však ještě jednu důležitou skupinu služeb, kterým se říká konstruktor, ten se však do diagramu nezapisuje.
Na konstruktor lze nahlížet jako na speciální metodu, kterou se vytváří instance třídy.
Také ve zdrojovém kódu se zápis konstruktoru podobá metodě s návratovým typem aktuální třídy – s tím rozdílem, že název metody se vynechá.
Podobně jako metody třídy, i konstruktor může mít své parametry, přitom jedna třída může obsahovat i více konstruktorů, pokud se budou lišit počtem či typem.
V takových případech hovoříme o přetížených konstruktorech.
Když konstruktor ve zdrojovém kódu chybí, kompilátor jazyka doplní jeden bezparametrický konstruktor automaticky.
Nový objekt se v kódu vytváří operátorem new
, za kterým následuje název třídy s případnými parametry konstruktoru, které jsou zapsány v kulatých závorkách:
Car automobile = new Car(); (1)
1 | Pomocí bezparametrického konstruktoru vytvoříme novou instanci třídy Car , kterou zapíšeme do proměnné automobile .
Případné parametry budou uvedeny v závorkách a při větším počtu se oddělí čárkou. |
3.10.6. Třída ve zdrojovém kódu
Teď se pokusíme přepsat grafický návrh třídy Car
do zdrojového kódu jazyka Java.
Protože nám teď půjde hlavně o strukturu kódu pro porovnání s diagramem, použijeme jen minimalistickou formu zápisu – což znamená, že tento kód lze přeložit kompilátorem.
Kvůli stručnosti omezíme popis jen na první dva atributy třídy.
Pro začátek oddělíme jednotlivé skupiny řádkovým komentářem, což je text uvedený dvěma lomítky, který se při dalším zpracování zdrojového kódu ignoruje.
Obsah této třídy najdete v souboru Car.java
ve složce přiloženého projektu s názvem Samples\src\test\java
(na Linuxu jsou lomítka opačná: Samples/src/test/java
).
Pro úplnost dodejme, že JRE rozpoznává (při práci se soubory) oba dva druhy lomítek.
class Car { (1)
// -------------------------
String modelName; (2)
Integer enginePower; (3)
// -------------------------
String getModelName() { (4)
return modelName; (5)
}
void setModelName(String name) { (6)
modelName = name; (7)
}
Integer getEnginePower() { (8)
return enginePower;
}
void setEnginePower(Integer power) {
enginePower = power;
}
}
1 | Název třídy koresponduje s horním blokem diagramu. Složená závorka za názvem vymezuje obsah třídy. |
2 | Seznam atributů odpovídá prostřednímu bloku diagramu.
Atribut třídy pro název vyrobeného modelu automobilu se jmenuje modelName a může obsahovat pouze objekty typu String .
Jeho deklarace je zakončena středníkem. |
3 | Na dalším řádku najdeme deklaraci celočíselného atributu pro evidenci výkonu motoru. Podobně bychom zapsali všechny zbývající atributy třídy. |
4 | Následuje přepis posledního bloku diagramu, který obsahuje seznam metod, začněme metodou pro čtení atributu.
Řádek začíná deklarací návratového typu metody, následuje její název.
Kulaté závorky mohou obsahovat seznam vstupních parametrů metody.
Bývá dobrým zvykem názvy metod uvádět malým písmenem, v případě čtení se používá prefix get . |
5 | Tělo metody obsahuje jediný příkaz, který poskytuje obsah atributu třídy, příkaz je zakončen středníkem.
Všimněme si, že datový typ atributu modelName se shoduje s návratovým typem metody. |
6 | Metoda pro zápis hodnoty začíná klíčovým slovem void , který deklaruje, že metoda nevrací nic.
V kulatých závorkách je deklarován jeden vstupní parametr name typu String . |
7 | Zápis hodnoty do atributu se formálně neliší od zápisu do proměnné. |
8 | Podle stejného vzoru bychom připravili metody pro čtení a zápis pro zbývající atributy. |
3.10.7. Vytvoření instance třídy a užití
Při volání konstruktoru JRE vyhledá a obsadí volnou část operační paměti RAM novou instancí třídy daného typu Car
a její referenci lze zapsat do proměnné stejného typu.
Tuto referenci si můžeme představit třeba jako poštovní adresu nějakého obytného domu.
Pokud někomu zkopírujeme adresu domu, jistě tím nevznikne nová stavba domu.
Analogicky, pokud zkopírujeme referenci objektu do jiné proměnné, nevznikne tím nová instance třídy Car
.
Protože už víme, že algoritmy se zapisují do metod, zapíšeme následující ukázku do metody createCar()
nějaké (servisní) třídy, která nám vrátí objekt jednoho fiktivního automobilu.
public Car createCar() { (1)
Car car = new Car(); (2)
car.setModelName("Auris"); (3)
car.setEnginePower(97); (4)
String model = car.getModelName(); (5)
Car theSameCar = car; (6)
boolean identical = car.getEnginePower() == theSameCar.getEnginePower(); (7)
return car; (8)
}
1 | Deklarace metody začíná klíčovým slovem public , které určuje rozsah viditelnosti této metody, podrobnosti zmíníme v kapitole o viditelnosti metod.
Následuje uvedení návratového typu Car a název metody s kulatými závorkami createCar() .
Závorky mohou obsahovat argumenty metody, tady však žádné nepotřebujeme.
Následuje pár složených závorek, které ohraničují zdrojový kód metody – ten se pro lepší čitelnost odsazuje mezerami. |
2 | S přiřazovacími příkazy jsme se už setkali.
Výraz na pravé straně vytvoří novou instanci třídy Car pomocí klíčového slova new a výsledek zapíšeme do proměnné car stejného typu.
Název proměnné může být odvozen od názvu třídy, ale může mít i jméno jiné.
Případné parametry konstruktoru by se uvedly v kulatých závorkách, v tomto případě zůstanou závorky prázdné. |
3 | Do objektu nastavíme model vozu prostřednictvím metody. Volání metody se zapisuje názvem proměnné objektu s názvem metody odděleným tečkou. Parametry metody se uvádí v kulatých závorkách, příkaz se zakončuje středníkem. |
4 | S využitím vlastnosti zvané autoboxing zapíšeme výkon motoru. Podobným způsobem bychom zapsali všechny zbývající atributy objektu. |
5 | Přiřazovací příkaz, který zapíše model auta z objektu car do proměnné model .
Protože metoda nemá žádné argumenty, uvedou se jen prázdné kulaté závorky.
Návratový typ metody se shoduje s typem proměnné.
Proměnnou model v této metodě nevyužijeme, její zavedení tedy nemá praktický význam.
Tato proměnná slouží jen pro ukázku čtení atributů, podobně jako na dalších řádcích. |
6 | Odkaz na nový objekt přiřadíme do nové proměnné. Připomeňme si, že tímto zápisem nevzniká nový objekt, pouze získáme další referenci k tomu stejnému objektu. |
7 | Připomeňme, že u objektů operátor == porovnává jejich instance, nikoli hodnoty. V tomto případě budou instance atributu enginePower identické. |
8 | Poslední řádek těla metody vrací hodnotu proměnné car , která se zapisuje za klíčovým slovem return . |
Za zmínku stojí několik postřehů:
-
Stále platí pravidlo, že instance nových objektů v jazyce Java vytváříme pomocí operátoru
new
, výjimku tvoří textové literály a objekty získané z primitivních typů automatickou konverzí autoboxing. Pokud vložíme objekt typuString
do konstruktoru třídyString
, získáme novou instanci tohoto typu se stejným obsahem, v praxi to má však jen malé využití. -
Na porovnání shody obsahu dvou objektů se používá metoda
equals()
s dodatkem, že algoritmus hodnocení shody se může lišit v závislosti na implementaci této metody. Operátor==
má význam pro porovnávání identických objektů. -
Tvorbu nových instancí (operátorem
new
) je možné přesunout do jiných metod, v některých případech se k tomu používají statické metody. Čím se tyto metody liší?
Statická metoda je metoda třídy, která nemá přístup k atributům ani metodám instance své třídy, může však používat další statické metody, nebo i statické atributy, což je pojem, který si vysvětlíme ještě v této kapitole.
Ve zdrojovém kódu se metoda deklaruje modifikátorem static
.
Statické metody tedy mají význam v případech, kde není třeba kontext objektu, při běžném návrhu tříd se však preferují metody instance.
int i = Integer.valueOf("465"); (1)
int v = Math.max(i, 100); (2)
LocalDate time = LocalDate.of(2010, 10, 22); (3)
1 | Metoda valueOf() je statická metoda třídy Integer , která převádí argument typu String na primitivní číslo.
Názvu metody předchází název třídy. |
2 | Tahle metoda vrací ze dvou číselných argumentů ten větší.
Pomocí statických metod třídy Math lze získat některé pokročilejší matematické operace včetně mocnin. |
3 | Metoda vytvoří objekt popisující lokální datum 2010-10-22 bez časové zóny. Parametry metody obsahují postupně: rok, měsíc a den. Metoda určená pro tvorbu nového objektu se někdy nazývá tovární metoda. |
Podobně jako statické metody třídy existují i statické atributy třídy a také se označují modifikátorem static
.
Názvy statických atributů se podle konvence zapisují velkými písmeny a víceslovné názvy se oddělují znakem podtržítka (_
).
Takové atributy mají využití často jako konstanty primitivních typů nebo neměnných objektů (anglicky immutable objects).
Typickým reprezentantem neměnných objektů je třída String
, která neposkytuje žádnou metodu pro změnu svého obsahu.
Neměnné objekty lze s výhodou bezpečně sdílet při zpracování v dalších metodách – včetně zpracování paralelního (souběžného).
Podrobnější popis zdrojového kódu třídy najdeme v kapitole První servlet.
3.10.8. Vztahy mezi třídami
Pojďme dál a představme si, že máme čtyři instance třídy Car
naplněné daty.
Pokud atributy každého objektu vypíšeme do jednoho řádku tabulky, získáme následující přehled:
Výrobce | Model vozu | Vyrobeno | Výrobní číslo motoru | Pohon | Velikost kufru v litrech | Vlastník |
---|---|---|---|---|---|---|
ŠKODA AUTO |
Fabia |
2005-06-01 |
123-654-987 |
benzin |
260 |
Jiří Mexedolid |
Škoda Auto |
Rapid |
2013-01-15 |
359-985-785 |
benzin |
550 |
Lucie Zixerfxodf |
Toyota |
Auris |
2010-10-22 |
987-654-123 |
Benzin |
354 |
Donald Choresisdi |
Ford |
Focus |
2015-03-30 |
958-232-497 |
benzin |
375 |
Donald Choresisdi |
V uvedené tabulce lze postřehnout rizika, která nám mohou zkomplikovat pozdější vyhledávání vozidel…
-
Nelze vyloučit, že jeden výrobce vozu bude v různých řádcích zapsán různými názvy, tento problém se týká i modelu, paliva či jména majitele. Tohle může dělat problémy při vyhledávání vozidel podle názvu výrobce.
-
Neumíme rozlišit dva různé majitele se stejným jménem a příjmením.
Můžeme sice namítnout, že odpovědnost za překlepy nese zapisovatel u klávesnice, ale problém naší datové nekonzistence to neřeší. Abychom takovým problémům předešli, rozšíříme datový model o další třídy.
-
Začneme třeba vlastníkem vozu a pro něho navrhneme novou třídu
User
(česky uživatel). Třída dostane jedinečný celočíselný identifikátor vlastníka (atributpersonalNumber
) pro případ duplicitních jmen osob. V diagramu pak vyznačíme vztah spojovací čárou, která bude na straně třídyCar
označena kosočtvercem, což je možné interpretovat slovy: objekt auta má vlastníka. Na konci vazby (vyznačené čárou) lze vyjádřit počet (přesněji multiplicitu) relačních objektů. Pevný počet se uvádí číslem, hvězdička vyjadřuje libovolný počet (včetně nuly), a lze vyjádřit také interval. Doplněný vztah tedy vypovídá, že jedno auto má právě jednoho vlastníka, ale jeden vlastník může mít více aut, nebo také žádné auto. Chybějící multiplicita sice formálně nevyjadřuje žádné výchozí hodnoty, v této knize to však lze považovat za ekvivalent právě zmíněného vztahu.Relace
(nebo také vazba či vztah, anglicky relation) se zapisuje ve zdrojovém kódu jako běžný atribut objektu. Pokud ho však v diagramu zakreslíme jako vztah, už se mezi běžnými atributy neuvádí, nemá to význam. Relace zakreslená v diagramu může mít svůj název, který se uvádí u spojovací čáry. Pokud název relace v diagramu chybí, lze za něj považovat jméno odkazované třídy (s malým písmenem na začátku). -
Podobně vyřešíme také model vozidla (
VehicleModel
) a označíme ho relací, která popisuje, že každý automobil má svůj model auta, ale jeden model auta může být označen u více vozidel. V tomto okamžiku už může být zřejmé, že výrobce automobilů je spíše vlastností továrního modelu než konkrétního automobilu, a proto ten atribut přesuneme do odpovídající třídy. -
Nakonec doplníme třídu
Company
a znázorníme její relaci k modelu auta (auto má výrobce).
Po zakreslení všech připomínek do původního diagramu může výsledek vypadat takto:
Podle diagramu připravíme nové třídy a upravíme také tu původní metodu pro vytvoření objektu jednoho auta. Podívejme se na výsledek…
public Car createCar() {
Car car = new Car();
car.setMade(LocalDate.of(2010, 10, 22)); (1)
VehicleModel model = new VehicleModel();
model.setManufacturer(new Company("Toyota", Locale.JAPAN)); (2)
model.setName("Yaris");
model.setTrunkVolume(436);
car.setModel(model); (3)
User user = new User();
user.setPersonalNumber(10);
user.setFirstname("Donald");
user.setSurname("Choresisdi");
user.setBirthDate(LocalDate.of(1990, Month.OCTOBER, 30)); (4)
car.setOwner(user); (5)
return car;
}
1 | Vytvořit objekt pro lokální datum pomocí číselných parametrů už umíme. |
2 | Ukázka použití konstruktoru se dvěma parametry, kde výraz JAPAN je statický atribut třídy Locale ze standardní knihovny Java.
Novou instanci třídy není třeba zapisovat vždy do proměnné, ale je možné ji rovnou poslat do argumentu metody, jak ukazuje tento řádek. |
3 | Instanci modelu vozu zapíšeme do objektu typu Car . |
4 | Objekt pro lokální datum lze vytvořit i alternativní metodou, kterou předáváme statickou konstantu reprezentující konkrétní měsíc v roce. Konstanta měsíce je opět součástí standardní knihovny Java. |
5 | Instanci objektu User zapíšeme do objektu typu Car jako vlastníka vozu. |
public void printCar() {
Car car = this.createCar(); (1)
LocalDate made = car.getMade(); (2)
String manufacturer = car.getModel().getManufacturer().getName(); (3)
String modelName = car.getModel().getName(); (4)
Integer trunkVolume = car.getTrunkVolume();
String firstname = car.getOwner().getFirstname();
String surname = car.getOwner().getSurname();
String owner = firstname + " " + surname; (5)
Printer.print(made, manufacturer,
modelName, trunkVolume, firstname, surname, owner); (6)
return; (7)
}
1 | První řádek metody readCar() zavolá metodu, kterou jsme si připravili před chvílí pro sestavení datového modelu automobilu, a uloží ji do proměnné car .
Klíčové slovo this značí, že metodu voláme na tom stejném objektu, jako byla volána metoda printCar() .
V případech tohoto typu je tohle chování výchozí a použití slova this lze vynechat.
Další příklady využití klíčového slova this najdete v kapitole Formulář uživatele. |
2 | Na tomto řádku získáme datum výroby automobilu z objektu automobilu do proměnné made .
Hodnota načtená z metody se tady sice nevyužije, ale mohla by sloužit například k nějakému tiskovému zpracování. |
3 | Podívejme se, jak lze volání metod řetězit.
Název výrobce auta z objektu typu Car získáme postupným voláním několika metod, které nás dovedou až k jeho jménu.
Konkrétně: výraz car.getModel() poskytne objekt typu VehicleModel , na kterém voláme metodu getManufacturer() pro získání objektu výrobce, ze kterého získáme jeho jméno.
Pokud v kódu uvedeme nesprávný název metody, při sestavení projektu nám kompilátor ohlásí chybu.
Šikovný editor (označuje se zkratkou IDE) zná strukturu tříd projektu, a tak chybný zápis může graficky včas vyznačit, anebo nabídnout nejbližší názvy metod existujících. |
4 | Jiná ukázka zřetězeného volání metod. |
5 | Spojením tří objektů typu String získáme jméno a příjmení vlastníka oddělené mezerou. |
6 | Příklad odeslání argumentů k nějakého dalšímu zpracování, v tomto případě dojde k zápisu parametrů na standardní výstup aplikace. |
7 | Zpracování metody se ukončuje pomocí klíčového slova return .
Pokud má metoda návratový typ void , pak příkaz return na konci metody je nepovinný a zpravidla se neuvádí. |
Všimněte si, že v diagramu přibyla nová třída popisující typ energie automobilu (například benzin, nafta, elektřina z baterie).
Může nás také zajímat, jak se zachová volání metody getName()
v případě, že metoda getManufacturer()
neposkytuje žádný přiřazený objekt.
Volání metod na neexistujícím objektu vyvolá chybu při běhu programu, která předčasně ukončí provádění metody.
Hodnota neexistujících objektů se v kódu popisuje klíčovým slovem null
a je výchozí (anglicky default
) hodnotou každého atributu třídy či pole v novém objektu.
Přesněji bychom mohli říci, že tento případ vede k vyhození výjimky.
Více informací o výjimkách (anglicky exceptions) se dozvíme v kapitole Výjimky.
Hodnotu null
lze také zapisovat do proměnných, posílat do metody či použít při porovnání instancí.
Přikládám několik příkladů použití.
public void writeNulls() {
Car car = createCar();
car.getOwner().setEmail(null); (1)
car.setOwner(null);
car = null; (2)
boolean undefinedCar = (car == null); (3)
}
1 | Ukázka zápisu hodnoty null do atributu vlastníka pomocí argumentu metody. |
2 | Ukázka zápisu hodnoty null do lokální proměnné. |
3 | Ukázka porovnání lokální proměnné s hodnotou null , v tomto konkrétním případě bude mít výraz (na pravé straně od rovnítka) hodnotu true . |
Hodnotu null
mohou obsahovat všechny objektové typy a pole, nikoliv však primitivní datové typy, které mají výchozí hodnotu 0
(nula, případně její ekvivalent).
Ukázky kódu, ve kterých se předchází chybám tohoto typu včetně řešení následků, zmíníme později v kapitole o výjimkách.
3.11. Další poznámky ke třídám
Teď už víme, že třída objektů má své jméno, atributy, metody a konstruktor. Zapamatujme si, že sloučení atributů a metod do jedné třídy se nazývá zapouzdření (anglicky encapsulation) a patří mezi důležité vlastnosti objektového programování, protože umožňuje návrh bezpečného, rozšiřitelného a znovupoužitelného zdrojového kódu.
3.11.1. Viditelnost metod, atributů a tříd
Některé metody třídy mají význam pouze pro aktuální třídu a hodilo by se zabránit jejich veřejnému využití – na rozdíl od metod jiných , které by měly být dostupné všem.
Z toho důvodu je možné přiřadit každé metodě takzvaný modifikátor, který deklaruje míru její viditelnosti ze strany objektů jiných tříd, přitom stejnými prostředky se deklaruje také viditelnost konstruktorů, atributů ale i celých tříd.
Jak se viditelnost označuje v diagramu tříd?
Znaménko minus (-) uvedené před atributem či metodou označuje lokální viditelnost, která omezuje použití pouze na metody své třídy a ve zdrojovém kódu se označuje klíčovým slovem private
.
Znaménko plus (+) označuje naopak veřejnou dostupnost, která se v kódu vyznačuje klíčovým slovem public
.
Protože přímý zápis do atributů objektu může ohrozit vnitřní datovou konzistenci objektu, bývají atributy obecně soukromé.
V případě potřeby stačí jejich hodnoty zprostředkovat vhodnou metodou (pro zápis nebo čtení).
Možná se nabízí otázka, jaký benefit vlastně mají tyhle soukromé metody? Jejich význam spočívá v tom, že takové metody lze snadno odstranit, přejmenovat, změnit jejich parametry či návratový typ – bez nutnosti upravovat důsledky takové změny v celém našem projektu, nebo i v projektech jiných, které jsou na tom našem závislé.
Označení viditelnosti v diagramu je nepovinné a v této knize lze považovat všechny neoznačené atributy v diagramech za private
a všechny neoznačené metody v diagramech za public
.
Podívejme se na úplný přehled viditelností (včetně dvou nových), které jsou seřazeny od nejvíce omezujících směrem k volně přístupným. Každá další viditelnost v tabulce zahrnuje též všechny předchozí viditelnosti.
Znak | Viditelnost | Klíčové slovo | Popis |
---|---|---|---|
- |
soukromá |
|
Pouze soukromý přístup v rámci třídy. Mezi různými instancemi stejné třídy však žádné omezení. |
~ |
balíček |
|
Metody a atributy bez deklarace modifikátoru viditelnosti lze použít pouze ze tříd stejného balíčku (anglicky package), v diagramu se označují znakem vlnovky (~). Tento modifikátor zmíníme ještě při popisu rozmístění tříd do balíčků. |
# |
chráněná |
|
Klíčové slovo jazyka se označuje v diagramu znakem mřížky (#), viditelnost umožňuje přístup potomkům třídy – což je pojem související s dědičností tříd a zmíníme ho za chvíli. |
+ |
veřejná |
|
Přístup bez omezení. |
3.11.2. Dědičnost
Vraťme se zpět k diagramu tříd evidence vozidel, popsanému v kapitole Vztahy mezi třídami. Mohlo by se zdát, že náš diagram je téměř hotový, ale do toho přijde zákazník, že by rád evidoval také motocykly a šlapací kola. Pro nás je to dobrá příležitost vysvětlit si dědičnosti tříd (anglicky inheritance).
Pro lepší představu o dědičnosti tříd je dobré začít hledáním odpovědí na otázky:
-
Co mají společného automobil a motocykl (z pohledu naší evidence)?
-
Čím se liší automobil a motocykl?
-
Co s nimi má společného šlapací kolo?
-
Čím se liší šlapací kolo od motocyklu?
Pokud najdeme nějaké společné vlastnosti, přesuneme je do nové třídy, která bude reprezentovat společného předka. Kandidáti na přesun pak mohou být:
-
SPZ (
licencePlate
) -
výrobní číslo motoru (
enginSerialNumber
) -
výkon motoru (
enginPower
) -
typ energie (
energyType
).
Novou třídu můžeme nazvat MotorVehicle
, což značí obecné motorové vozidlo.
Dále můžeme hledat společné vlastnosti bicyklu a motorových vozidel a shodu najdeme zřejmě v atributech:
-
datum vyrobení (
made
) -
model výrobku (
model
) -
vlastník (
owner
).
Založíme tedy novou třídu s názvem RoadVehicle
(silniční vozidlo).
V původní třídě Car
zbude pouze velikost kufru (trunkVolume
), protože u ostatních vozidel tato vlastnost našeho zákazníka nezajímá.
Pro bicykl založíme ještě třídu Bike
, která může evidovat velikost rámu bicyklu (v palcích), a pro motorky založíme třídu Motorcycle
, která nemusí obsahovat žádný přidaný atribut.
Když všechny třídy zakreslíme do nového diagramu, výsledek může vypadat takto:
Pokud některá třída dědí vlastnosti (metody, atributy) některého rodiče, pak tohoto rodiče označí uzavřenou šipkou, kterou můžeme interpretovat slovem je. Všechna následující tvrzení jsou pravdivá:
-
Automobil je motorové vozidlo.
-
Motorové vozidlo je silniční vozidlo.
-
Automobil je silniční vozidlo.
-
Bicykl je silniční vozidlo.
-
Bicykl však není motorové vozidlo (nebo ho nechceme evidovat jako motorové vozidlo).
Nadále však platí všechny původní vztahy (asociace):
-
Automobil má motor.
-
Motorové vozidlo má motor.
-
Motorové vozidlo má vlastníka.
-
Bicykl má vlastníka.
-
Bicykl však nemá motor (nebo ho nechceme evidovat jako motorové vozidlo).
Potomek třídy má tedy k dispozici všechny metody rodičů, pokud jsou typu public
nebo protected
.
Potomek však může mít svoji vlastní implementaci stejné metody, pokud má metoda stejné jméno, vstupní parametry i návratový typ.
V reálném životě si můžeme takové chování přiblížit na zjednodušeném ovládání automobilu: pro zrychlení používáme pedál plynu, pro zpomalení brzdu a pro změnu směru volant, řidiče však nemusí zajímat detailní funkce spalovacího motoru, brzdového systému či převodu pohybu volantu na natočení kol.
Právě díky takové vlastnosti může řidič zpravidla používat automobily různých značek a výrobců bez větších problémů.
Ukázku překrývání metody najdeme v kapitole Piškvorky s defenzivní strategií hry.
Této vlastnosti se říká polymorfismus (anglicky polymorphism) a jedná se o významnou vlastnost objektově orientovaného programování. Technika překrývání metod se označuje anglickým slovem overriding. |
Teď nás může zajímat, jak se změní použití upravené třídy Car
po zavedení dědičnosti. Jedna dobrá zpráva: z pohledu obsluhujícího kódu je použití třídy Car
identické bez ohledu na to, kolik má rodičů a ve kterém předkovi metoda právě leží.
Pro připomenutí zápisu a čtení se tedy můžeme vrátit k ukázkám zdrojového kódu v kapitole Vztahy mezi třídami, protože tento kód zůstane stejný. Při zavedení dědičnosti se nám však otevírají nové možnosti, seznam těch nejdůležitějších je tady:
-
Každá třída (jazyka Java) má právě jednoho rodiče, výjimkou je prarodič všech tříd s názvem
Object
(pozor, nezaměňovat s obecným názvem instance třídy, pro začátečníka může být podobnost slov zavádějící). -
Název rodiče se v kódu zapisuje za název třídy pomocí klíčového slova
extends
, a pokud třída nemá žádného výslovně označeného rodiče, Java kompilátor tam automaticky dosadí tříduObject
. -
Třída
Object
má několik metod, mezi nejpoužívanější patří metodatoString()
, která poskytuje textovou informaci o vnitřním stavu objektu. Každý potomek třídy může obsahovat jinou implementaci metody a u tříd našeho projektu implementace záleží jen na nás. -
Polymorfismus se týká pouze metod označených viditelností
public
aprotected
. -
Konstruktor se nedědí.
-
Potomek může používat atributy a metody rodiče s některou z viditelností:
protected
,package
aprivate
. -
Pokud chceme zakázat dědičnost konkrétní třídy, označíme ji klíčovým slovem
final
, pokud chceme zakázat překrytí naší konkrétní metody, lze ji také označit klíčovým slovemfinal
. Typickým příkladem třídy, které není dovoleno vytvářet žádné potomky, jeString
pro reprezentaci textů.
3.11.3. Výjimky
Při zpracování nějakého algoritmu (v metodě třídy) se můžeme dostat do stavu, kdy neznáme postup dalšího zpracování.
V takových případech nezbývá než zpracování předčasně ukončit.
Často však potřebujeme ukončit celou hierarchii volání metod – a pro tento případ se používá technika vyhazování výjimek.
K přerušení dojde zápisem operátoru throw
následovaným objektem typu Throwable
.
Běžně se však používá nějaký potomek této třídy, jehož jméno lépe koresponduje s typem problému.
Následuje ukázka zdrojového kódu, který vyhazuje výjimku:
public void readCar() throws IllegalStateException {
Car car = createCar();
if (car.getOwner() == null) {
throw new IllegalStateException("Missing owner"); (1)
}
String surname = car.getOwner().getSurname();
}
1 | Vyhození nové instance třídy typu IllegalStateException . |
Před zpracováním objektu typu Car
chceme mít jistotu, že náš objekt je sestaven v souladu s dokumentací, a tak otestujeme vlastníka metodou getOwner()
.
Pokud vlastník chybí, pak pomocí klíčového slova throw
vyhodíme instanci třídy IllegalStateException
, čímž běh metody ukončíme.
Objekt výjimky se propaguje ven z metody skrz všechny nadřazené metody (ze kterých byla naše metoda volána) – až do okamžiku zachycení výjimky.
Každá metoda přitom může (někdy musí, viz dále) obsahovat seznam výjimek, které algoritmus může vyhazovat.
Seznam výjimek se uvádí klíčovým slovem throws
) a zapisuje se za argumenty metody (ohraničené kulatou závorkou).
Pokud výjimku nezachytí kód stávající či žádné nadřazené metody, celá aplikace spadne (ukončí se s chybou).
Ukázka zachycení výjimky následuje…
public void makeCar() {
try { (1)
new CarService().readCar();
} catch (Exception e) { (2)
e.printStackTrace(); (3)
}
String message = "to continue ...";
}
1 | Zahájení bloku pro zachytávání výjimek. |
2 | Konec bloku pro zachytávání výjimek typu Exception (včetně potomků). |
3 | Metoda výjimky zapíše popis fragmentů zdrojového kódu (říká se jí anglicky stacktrace) na standardní výstup. |
Každou výjimku lze zachytit, pokud se její instance vyhodí (operátorem throw
uvnitř bloku try-catch
).
Teď se nabízí otázka: co by se stalo, kdybychom chybějícího vlastníka netestovali, a rovnou zavolali metodu car.getOwner().getSurname()
?
V okamžiku, kdy JRE zjistí požadavek na volání metody neexistujícího objektu (za běhu aplikace), vyhodí výjimku typu NullPointerException
.
Výjimky jsou objekty podobné ostatním objektům jazyka Java, přitom jejich společným rodičem je třída Throwable
.
Jejich instance tedy vytváříme operátorem new
, můžeme je zapisovat do proměnných, můžeme vytvářet své vlastní instance, a dokonce i vlastní třídy (jako potomky).
Podívejme se na diagram zobrazující typovou hierarchii několika tříd ze standardní knihovny JRE, strukturu tříd lze ověřit v generované dokumentaci JavaDoc [5].
Třídy v diagramu vyznačené tmavší barvou označují takzvané kontrolované výjimky, které zmíníme za chvíli.
Popišme si základní typy výjimek:
-
Throwable
– rodič všech výjimek může obsahovat textový popis chyby, může obsahovat referenci na instanci výjimky, která byla předchůdcem problému a umí vytisknout stacktrace na standardní výstup, jenž obsahuje technický zápis volání jednotlivých metod, umožňujících lokalizovat zdrojový řádek chyby. Tato třída má pouze dva přímé potomky. -
Error
– výjimky tohoto typu vyhazuje obvykle JRE, v kódu se zpravidla nezachycují, a tak vedou k pádu aplikace. Pro představu uveďme dva z jejích potomků:-
OutOfMemoryError
– JRE nemá dostatek paměti RAM (zpravidla při vytváření nových objektů). -
StackOverflowError
– dochází k rekurzivnímu volání metod do takové úrovně, kterou JRE vyhodnotí jako chybu.
-
-
Exception
– reprezentuje výjimky, které má smysl zachytávat (pomocí výrazůtry-catch
), uveďme jen pár potomků:-
PrintException
– obecná výjimka vznikající při tisku. -
IOException
– chyba vznikající při čtení/zápisu do souborů (například na disku).
-
-
Nekontrolované výjimky – pokud existuje významné riziko, že metoda vyhodí výjimku, je třeba ji deklarovat za názvem metody pomocí klíčového slova
throws
. Existují dva typy výjimek (Error
aRuntimeException
), které se v metodách deklarovat nemusí, a tohle pravidlo platí také pro všechny jejich potomky. Takovým výjimkám se říká nekontrolované výjimky (anglicky unchecked exceptions) a těm ostatním kontrolované výjimky (anglicky checked exceptions). Deklarace kontrolovaných výjimek je v metodě nezbytná, jinak kompilace kódu skončí chybou. Uveďme ještě několik příkladů nekontrolovaných výjimek:-
IllegalArgumentException
– výjimka se používá ke kontrole platnosti argumentů metod. -
IllegalStateException
– výjimka se používá ke kontrole platnosti stavů objektů. -
NullPointerException
– výjimku vyhazuje JRE při nedovoleném volání metod nedefinovaného objektu. Chyby tohoto typu mají přerušit zpracování algoritmu, ale neměly by vést k pádu celé aplikace.
-
Se znalostí diagramu tříd lze zachytávat pouze určité typy výjimek – včetně jejich podtypů. Vyhazování výjimek má zvýšené nároky na zpracování, a tak není doporučováno tímto mechanismem řídit běžné algoritmy.
Za mimořádnou událost lze považovat algoritmus ve stavu, kdy neumí řešit vzniklou situaci v rámci svých kompetencí. Příkladem může být zakázaný obsah parametrů metody nebo jiná situace v rozporu s dokumentací. |
3.11.4. Balíčky
Aby kompilátor jazyka Java mohl třídu bezpečně identifikovat, její název musí být jednoznačný.
Pro eliminaci kolize stejných názvů tříd různých knihoven a dodavatelů se třídy ukládají do takzvaných balíčků (anglicky package).
Balíčky mají stromovou strukturu, kde listy
stromu jsou třídy jazyka Java.

Překreslením jabloně z kapitoly Stromová struktura dat do podoby prezentované editorem NetBeans bychom dostali dva nové pohledy na strom, kde kořen stromu má prázdný název, zápis tříd do kořene balíčků však nepatří mezi doporučovaná řešení. Do obrázků se vešlo znázornění pouze horní části stromu, zbývající část je analogická. Levý pohled připomíná běžnou adresářovou strukturu z nějakého správce diskových souborů, pro druhý pohled je typický výpis úplné cesty ke každé skupině tříd, které tvoří listy stromu. V integrovaném vývojovém prostředí NetBeans je možné si vybrat způsob zobrazení podle osobních preferencí. Třídy uložené v jednom balíčku musí mít unikátní název, přitom použití velkých a malých písmen v jeho názvu je významné. Balíčky však nemusí obsahovat jen třídy, ale (v případě potřeby) také různé textové šablony, soubory s jazykovými lokalizacemi. Takové soubory se nazývají obecně zdroje (anglicky resources). Pro jistotu připomínám, že v IDE zapisujeme třídy objektu, nikoli jejich instance, ty vznikají až při spuštění projektu.
Pro zajištění unikátních názvů balíčků se osvědčilo odvozovat názvy uzlů (u kořene stromu) od registrovaných internetových domén. Obecné doporučení pak zní: struktura balíčku se sestavuje z následujících částí:
-
název domény prvního řádu,
-
název domény druhého řádu (může reprezentovat název organizace),
-
název projektu,
-
Pak mohou následovat moduly a další členění v rámci našeho projektu.
Oddělovacím znakem je tečka, a protože uvedené pojmenování je pouze doporučení, v projektech pro vlastní potřebu si můžeme názvy balíčků vymyslet jinak. Úplné označení třídy žlutého listu by pak mohlo vypadat takto:
a.e.f.g.YellowLeaf
V ideálním případě by
-
znak
a
reprezentoval internetovou doménu prvního řádu, -
znak
e
reprezentoval doménu druhého řádu, -
zbytek je členění v rámci projektu podle vlastního vkusu, pro lepší čitelnost doporučuji inspirovat se některým hotovým projektem.
Pokud pomineme (nedoporučované) ukládání tříd do kořene balíčků, platí, že název balíčku třídy ve zdrojovém kódu Java se uvádí jako první informace, která začíná klíčovým slovem package
, a definice je zakončena středníkem.
Následuje formální výčet všech tříd (včetně balíčků), které se vyskytují ve třídě bez názvu balíčků, k tomu slouží klíčové slovo import
.
Každý import se zapisuje na nový řádek, jejich pořadí je nevýznamné.
Podívejme se na první řádky zdrojového kódu třídy Car
:
package net.ponec.jbook.plainSamples.domains; (1)
import java.time.LocalDate;(2)
import java.lang.String;(3)
import java.lang.Integer;
1 | První řádek obsahuje balíček třídy, tuto deklaraci mohou předcházet pouze komentáře, které jsou pro kompilátor nevýznamné. |
2 | První import definuje třídu LocalDate z balíčku java.time . |
3 | Importy z balíčku java.lang jsou nepovinné a ve zdrojovém kódu se neuvádí, kompilátor je doplní automaticky.
V tomto balíčku se nachází také třídy String a Integer . |
V importech se mohou vyskytovat pouze třídy unikátního jména, v případě kolize stejných tříd z různých balíčků nezbývá než jednu skupinu doplnit názvem balíčku u definice třídy. Praktická ukázka deklarace balíčků a importu je v kapitole Hello, World!.
3.11.5. Interface
Pojem interface (česky rozhraní) definuje (v jazyce Java) veřejné metody objektu – včetně parametrů a návratových typů. Interface nemá vlastní konstruktor ani atributy, může však obsahovat veřejné statické konstanty a od verze Java 8 také výchozí implementaci metod. Rozhraní se zapisuje v Java kódu podobně jako běžné třídy, jen implementace metod může být prázdná (místo složených závorek s obsahem se uvede pouze středník). Na rozdíl od Java tříd proto nelze vytvářet jeho instance přímo, ale je vždy třeba nejdříve vytvořit nějakou třídu, která jeho chybějící metody implementuje. Zatímco každá třída může dědit pouze z jedné třídy, implementovat může hned několik rozhraní.
public interface CounterApi { (1)
void add(int i); (2)
int getCount();
}
1 | Na místě klíčového slova class se uvádí interface . |
2 | Metoda nemá implementaci (ve složených závorkách). |
Třída, která rozhraní implementuje, pak uvede (v deklaraci třídy) klíčové slovo implements
podle následujícího vzoru:
public interface CounterImpl implements CounterApi { ... }
Implementované metody musí být vždy veřejné, viz kapitola o viditelnosti.
3.11.6. Inline třídy a lambda výrazy
Při zmínce o rozhraní (anglicky interface) jsme se dozvěděli, že bez její implementace (konkrétně třídy) nelze vytvořit ani instanci.
Pro zjednodušení některých zápisů umožnili autoři jazyka Java sestavovat třídy přímo na řádku zdrojového programu – podívejme se na ukázku sestavení třídy pomocí jednoho rozhraní s generickým typem String
:
Consumer<String> consumer = new Consumer<String>() {
@Override
public void accept(String t) {
doSomething(t);
}
};
Pokud získáme proměnnou consumer
, můžeme zavolat její metodu accept()
, která předaný parametr buď zpracuje sama, nebo úkol deleguje na některou z metod hostitelské třídy.
Pamatujme, že in-line třídy mají neomezený přístup také k soukromým metodám (i atributům) hostitele – viz kapitolu o viditelnosti metod.
Protože zápis in-line tříd je stále poměrně pracný, vývojáři jazyka zapracovali od kompilátoru Java 8 zjednodušení, kde dojdeme ke stejnému výsledku jediným řádkem:
Consumer<String> consumer = (String t) -> doSomething(t);
První závorka za rovnítkem označuje argument metody accept()
, výraz za operátorem ->
obsahuje implementaci metody. Pokud by metoda obsahovala více příkazů, je třeba ji obalit složeným závorkami.
Aby kompilátor poznal, že má implementovat právě metodu accept()
, musí interface obsahovat popis právě jedné metody.
Pro úplnost doplňme, že rozhraní Java verze 8 mohou obsahovat ještě výchozí implementace metod, kterými se však tady zabývat nebudeme.
Lambda výrazy se často používají ve spojení s třídou Stream (zmíníme ji později), ukázku použití najdeme také v kapitole Bodové kreslení.
3.11.7. Knihovny
Knihovny v jazyce Java představují (zpravidla) skupinu zkompilovaných tříd, které pokrývají obvykle určitý tematický okruh řešení v souladu s dokumentací knihovny. Součástí takové knihovny bývá běžně dokumentace popisující použití důležitých tříd knihovny a jejich metod (včetně parametrů), chování, typické použití a další informace významné pro správné použití knihovny. Výhodou použití knihoven není jen úspora času vývojářů, ale i jednotný styl řešení, který vzniká akceptováním programového rozhraní knihovny, což vede k lepší orientaci ve zdrojovém kódu napříč různými projekty, pokud se v nich ty stejné knihovny také používají. Aby však nedocházelo ke konfliktům názvů tříd z různých knihoven, je důsledné využívání vlastností balíčků (z předchozí kapitoly) nezbytné.
Značné oblibě se pro svou snadnou dostupnost těší zejména osvědčené knihovny s licencí open‑source, uložené v repozitářích Mavenu.
Mnoho užitečných tříd nabízí už základní knihovna, která je součástí JRE.
Knihovny v projektech Maven se připojují pomocí takzvaných závislostí (anglicky dependences), které se zapisují do konfiguračních souborů formátu XML s názvem pom.xml
.
V repozitáři Maven je každá knihovna definována textovým identifikátorem skupiny (groupId
), identifikátorem knihovny (artifactId
) a číslem verze (version
).
Maven si při prvním sestavení našeho projektu stáhne z internetu (do našeho domovského adresáře) potřebné knihovny (závislosti), které zde zůstanou k dispozici pro případné pozdější využití.
Reálnou ukázku připojení knihovny lze najít v kapitole Datový model HTML stránky.
3.11.8. Komentáře kódu a JavaDoc
Již dříve jsme zmínili, že řádkový komentář začíná dvojicí lomítek a končí koncem řádku.
V jazyce Java se používají ještě víceřádkové komentáře, které začínají dvojicí znaků /*
a končí znaky */
, tyto komentáře nelze do sebe vnořovat.
JavaDoc jsou speciální víceřádkové komentáře určené pro generování technické dokumentace Java projektu ze zdrojového kódu. Popisovat lze samotné třídy, jejich atributy, metody (včetně parametrů a návratových typů), a dokonce i celé balíčky. Zápis se podobá víceřádkovým komentářům, pouze první hvězdička se zdvojí. Následuje ukázka použití JavaDoc nad metodou, která by měla vyhledávat automobil podle výrobního čísla motoru.
/** (1)
* Find a car by its engine number (2)
* @param motorSerialNumber Serial number of the motor (3)
* @return The required car or the {@code null} value, (4)
* of no card was found.
*/ (5)
public Car findCar(Integer motorSerialNumber) {
throw new UnsupportedOperationException("Not implemented yet"); (6)
}
1 | Začátek komentáře typu JavaDoc. |
2 | Popis použití metody či jejího chování může mít i několik řádků.
Technické termíny lze formátovat výrazem dle vzoru {@code null} , některé další možnosti formátování textu jsou odvozeny od značkovacího jazyka HTML. |
3 | Popis argumentu se uvádí výrazem @param , v případě více argumentů každý popis začíná na novém řádku v odpovídajícím pořadí. |
4 | Popis návratové hodnoty se uvádí výrazem @return . |
5 | Znaky ukončující víceřádkový komentář. |
6 | Pokud implementace metody chybí, je vhodné v těle metody vyhazovat výjimku, alternativní návrat hodnoty null totiž může působit za běhu programu dojmem, že implementace je hotová, jen auto nebylo nalezeno pro daný parametr. |
JavaDoc nad projektem lze sestavit příkazem Maven:
mvn javadoc:javadoc
JavaDoc se generuje do souborového systému ve formátu HTML, kterému rozumí internetový prohlížeč.

Obrázek je pouze ilustrační, jeho obsah byl zkrácen pro potřeby tisku. V horní části stránky je hlavička s navigační lištou, která zobrazuje verzi Java a nabízí také pole pro vyhledávání tříd či balíčků. Pod ní je uvedeno označení modulu, jedná se o členění tematicky podobných balíčků a zdrojů do skupin pro snadnější distribuci aplikace. Balíčky byly zavedeny od verze Java 9, a v této knize s tímto pojmem pracovat nebudeme; podrobnější informace lze dohledat na internetu pomocí klíčových slov Java Platform Module System (JPMS). Následuje uvedení názvu balíčku, třídy a znázornění dědičnosti. Dále vidíme popis všech rozhraní, která třída implementuje. Na stránce pak najdeme detailní popis třídy, někdy i doporučené použití. Obrázek naznačuje také popis některých metod objektu, v reálné dokumentaci najdete informací mnohem více.
3.12. Datový typ pole
Už jsme zmínili primitivní datové typy s objekty a poslední důležitý datový typ je pole, které může obsahovat seznam objektů nebo hodnot primitivních typů. Datový typ pole obsahuje řadu hodnot stejného datového typu a pevné délky. Na jednotlivé prvky pole lze odkazovat pořadovým číslem uvedeným v hranaté závorce (za názvem proměnné), kterému se říká index, jeho číslování začíná v jazyce Java vždy od nuly. I když se může někomu zdát takový počátek číslování zpočátku nezvyklý, na praktických příkladech se brzy přesvědčíme, že tohle pojetí dobře zapadá do algoritmů, které s polem běžně pracují. Pole znaků se hodí typicky v případech, kdy potřebujeme z jednotlivých znaků složit slovo, nebo potřebujeme evidovat nějakou konečnou řadu čísel. Podívejme se na příklady použití jednorozměrného pole znaků, podobně bychom však pracovali i s polem libovolných datových typů.
# | Kód | Výsledek | Typ | Poznámka |
---|---|---|---|---|
1. |
|
A, B, C |
char[] |
Deklarace datového typu pole začíná uvedením typu prvku následovaného párem hranatých závorek |
2. |
|
N, E, X, T |
char[] |
Pokud vytváříme pole do již zavedené proměnné, je třeba před složenou závorku vložit výraz typu |
3. |
|
■, ■ |
char[] |
Výraz vytvoří prázdné pole požadované velikosti.
Všechny prvky číselného primitivního typu budou obsahovat hodnotu |
4. |
|
O, K |
char[] |
Do každého prvku zapíšeme jeden znak. |
5. |
|
2 |
int |
Výraz poskytující délku pole. |
6. |
|
O |
char |
Pomocí toho výrazu přečteme hodnotu prvního prvku. Tento výraz lze přiřadit proměnné typu |
7. |
|
K |
char |
Čtení posledního prvku pole. Čtení z prvků pole mimo rozsah pole by skončilo výjimkou. |
8. |
|
H, E, L, L, O |
char[] |
Znakové pole můžeme získat z objektu typu |
9. |
|
E, H, L, L, O |
char[] |
Obsah pole je možné seřadit od nejmenších hodnot po největší zavoláním metody |
10. |
|
EHLLO |
String |
Nový |
11. |
|
AB, CD, EF |
String[] |
Poslední příkaz demonstruje, jak z pole znaků můžeme sestavit zase objekt typu |
Další ukázky použití lze najít v přiložených příkladech, více informací je uvedeno v kapitole Programové výrazy. Pro úplnost dodejme, že Java podporuje i vícerozměrná pole, jejich použití si však ukážeme později.
Od verze Java 5 lze vytvořit pole také voláním metody s parametrem proměnlivé délky, který se někdy označuje zkratkou varargs (z angličtiny: variable arguments).
Pole vzniká převzetím skupiny parametrů parametrem metody, který musí být výhradně posledním parametrem metody.
Syntaxe zápisu argumentu proměnné délky vychází ze zápisu jednorozměrného pole, pouze pár hranatých závorek za typem pole ([]
) nahradíme třemi tečkami (...
).
Podívejme se na implementaci jednoduché metody, která pouze převádí parametry na pole.
String[] toArray(String... texts) { (1)
return texts; (2)
}
1 | Metoda toArray() má parametr typu String proměnné délky. |
2 | V těle metody už pracujeme s parametrem stejně, jako s polem deklarovaným výrazem String[] .
Metoda vrací vstupní parametr jako běžné textové pole, metoda však může implementovat také libovolné jiné zpracování tohoto parametru. |
String[] words = toArray("Hello", "Word!"); (1)
1 | Metoda toArray() vrací parametry jako pole, které zapíšeme do proměnné words . |
3.13. Vybrané třídy standardní knihovny
Standardní knihovna jazyka Java má k dispozici značné množství tříd a rozhraní, které se liší svým zaměřením, a některé z nich si teď představíme.
3.13.1. Třída String
Připomeňme si, že objekt typu String
reprezentuje neměnnou sekvenci znaků, a tak může obsahovat i běžná slova či věty.
Tento objekt nabízí metody pro vyhledávání znaků či textů, metody pro slučování dvou objektů, na rozdíl od pole znaků však jednou sestavený objekt typu String
nelze měnit.
Požadavky na změnu obsahu pak řešíme sestavením nové objektu typu String
, který lze vložit i do té stejné proměnné.
# | Kód | Výsledek | Typ | Poznámka |
---|---|---|---|---|
1. |
|
Joe |
String |
Výraz ve dvojitých uvozovkách reprezentuje text objektového typu |
2. |
|
Joe Black |
String |
Spojení dvou Stringů operátorem |
3. |
|
J |
char |
Čtení prvního znaku. |
4. |
|
o |
char |
Čtení znaku na další pozici. Pokud metoda dostane index mimo rozsah, vyhodí výjimku. |
5. |
|
9 |
int |
Celkový počet znaků Stringu. |
6. |
|
4 |
int |
Metoda vrací pořadí (index) prvního shodného znaku zleva, jinak hodnotu |
7. |
|
Black |
String |
Metoda vytvoří nový objekt, který obsahuje sekvenci znaků od 4. (včetně) do znaku před 9. indexem. K dispozici je i přetížená metoda s jediným parametrem pro počátek, konec se vypočítá z délky hodnoty |
8. |
|
JOE BLACK |
String |
V novém objektu budou malé znaky převedeny na velké. |
9. |
|
joe black |
String |
V novém objektu budou velké znaky převedeny na malé. |
10. |
|
true |
boolean |
Metoda |
11. |
|
false |
boolean |
Operátor |
12. |
|
true |
boolean |
Dvě stejné proměnné ukazují vždy na stejnou instanci objektu. |
13. |
|
true |
boolean |
Ověření začátku textu. |
14. |
|
true |
boolean |
Ověření konce textu. |
15. |
|
true |
boolean |
Metoda |
16. |
|
This is Joe Black |
String |
Metoda určená pro spojení dvou objektů typu |
17. |
|
This is Joe Black ! |
String |
Spojování řetězců statickou metodou |
18. |
|
This is Joe Black! |
String |
Spojování objektů pomocí šablony, která se uvádí na místě prvního parametru. Značka |
3.13.2. Rozhraní List
Výhodou datového typu pole je rychlost v kombinaci s poměrně příznivými paměťovými nároky, což platí zejména u polí primitivních typů.
V praxi by se však občas hodilo, kdybychom nemuseli deklarovat počet prvků předem, ale velikost by se přizpůsobila dynamicky při vkládání prvků nových nebo odebírání prvků nepotřebných.
Takové objekty jsou naštěstí součástí standardní Java knihovny, přičemž jejich možnosti definuje interface List
.
Zapamatujme si, že prvky objektu List
jsou výhradně objekty (nebo i pole), nikoli však primitivní typy.
V našich ukázkách se zaměříme pouze na implementaci ArrayList
, která je rovněž součástí standardní knihovny.
# | Kód | Výsledek | Typ | Poznámka |
---|---|---|---|---|
1. |
|
ArrayList |
Vytvoříme nový objekt typu |
|
2. |
|
8, K |
ArrayList |
Do vytvořeného objektu zapíšeme postupně dva prvky různých typů. Pro zápis využijeme autoboxing. |
3. |
|
2 |
int |
Přidáním každého prku se délka listu zvětší, aktuální délku pole zjistíme metodou |
4. |
|
8 |
Integer |
První hodnotu listu získáme metodou |
5. |
|
K |
Character |
Obsah hodnoty na pozici |
Protože prvky třídy List
jsou objekty (případně pole), mohou obsahovat také hodnotu null
.
Třída ArrayList
Jak už název třídy napovídá, vnitřní implementace třídy ArrayList
je postavena na poli objektů, přitom interně obsahuje dva důležité atributy:
-
elementData
typuObject[]
je pole s výchozí velikostí deseti (10
) prvků, pokud v konstruktoru není uveden požadavek na velikost jinou, -
size
je celočíselný atribut obsahující počet skutečně vložených prvků, výchozí hodnota je nula (0
).
Po vytvoření objektu výchozím konstruktorem třídy ArrayList
si můžeme schematicky popsat chování toho objektu pro několik scénářů:
-
Pokud do prázdného listu vložíme metodou
add()
první hodnotu, metoda zapíše hodnotu na pozici prvního prvku pole (s indexem0
) a atributsize
se zvýší na hodnotu1
. -
Další hodnota (vložená stejnou metodou) bude zapsána do pole na pozici určené aktuální hodnotou atributu
size
a velikost seznamu se pak zvýší o jedničku. Takové vkládání hodnoty lze ještě několikrát opakovat. -
Při vkládání jedenácté (
11
) hodnoty však máme všechny prvky pole obsazené, co s tím? V takovém případě musí metodaadd()
vytvořit nějaké nové a větší pole, nakopírovat do něj všechny hodnoty pole původního a výsledek zapsat do atributuelementData
, čímž se to původní pole připraví automaticky k uvolnění z paměti. Další postup už bude shodný s předchozím krokem. -
Pokud budeme chtít metodou
remove()
odstranit hodnotu na pozici3
, metoda musí přesunout všechny hodnoty zapsané pod vyšším indexem o jednu pozici níže, přitom hodnota atributusize
se o jedničku sníží.
Z uvedeného popisuje je zřejmé, že použití třídy ArrayList
je optimální v případech, kdy umíme zhruba odhadnout cílový počet prvků a kdy četnost odebírání (či vkládání) prvků není vysoká, zejména na počátečních pozicích to má počítač pracnější.
V opačných případech je vhodné zvážit implementaci jinou, která však může mít vyšší nároky na paměť.
Generické datové typy
Použitím datového typu List
jsme získali výhodu dynamického přizpůsobení velikosti, ale přišli jsme o typovou kontrolu prvků – ve srovnání s polem.
Pro eliminaci tohoto hendikepu byly do jazyka Java 5 přidány takzvané generické datové typy.
Generický typ je tedy doplňková informace třídy (či metody), kterou lze využít pro upřesnění objektového typu některé části třídy (či metody) v době kompilace.
Protože taková definice význam generik asi moc nepřiblíží, zaměřme se raději na příklady použití.
List s typem prvku
Řekněme, že potřebujeme objekt typu List
pro prvky typu Character
.
Standardní rozhraní List
má naštěstí už implementovanou podporu deklarace typu prvku a využívá k tomu právě zmíněné generické typy.
Ve zdrojovém kódu se generický typ uvádí za názvem běžného rozhraní (nebo třídy) ve špičatých závorkách <>
, více generických typů se odděluje čárkou.
Použití generických typů není povinné, ale je silně doporučené.
Podívejme se na ukázky zdrojového kódu.
# | Kód | Výsledek | Typ | Poznámka |
---|---|---|---|---|
1. |
|
ArrayList |
Vytvoříme nový objekt typu |
|
2. |
|
O, K |
ArrayList |
Do vytvořeného objektu zapíšeme postupně dva prvky typu |
3. |
|
O |
Character |
Čtení prvků je analogické. |
4. |
|
K |
Character |
Prvky však lze zapsat přímo do proměnné toho generického typu. |
5. |
|
H, E, L, L, O |
…ArrayList |
Statická metoda třídy |
6. |
|
E, H, L, L, O |
…ArrayList |
Ukázka jednoduchého abecedního řazení prvků. |
7. |
|
AB, CD, EF |
…ArrayList |
Generický List lze vytvořit pro objekty různých typů (včetně |
3.13.3. Rozhraní Map
Další zajímavé rozhraní se jmenuje Map
a my se teď zaměříme pouze na jeho implementaci TreeMap
.
Při zápisu hodnoty do objektu toho typu (a jejím čtení) můžeme shledat jistou podobnost s objekty typu List
, jen místo celočíselných indexů se používají objekty zvané klíče.
Pro efektivní fungování mapy musí mít tyto klíče schopnost rozpoznat své místo v hodnotové řadě.
Tuto vlastnost garantuje rozhraní Comparable
, jehož metoda dokáže vyhodnotit, který ze dvou klíčů má vyšší pozici nebo jedná-li se o klíče stejné hodnoty.
Rozhraní Comaparable
mají implementované všechny objektové ekvivalenty primitivních typů a třída String
, proto můžeme jejich instance snadno použít jako klíč mapy.
Pokud bychom potřebovali pro klíč využít nějaký vlastní objekt, stačí zmíněné rozhraní implementovat.
# | Kód | Výsledek | Typ | Poznámka |
---|---|---|---|---|
1. |
|
{3=O, 7=K} |
TreeMap |
Mapa obsahuje (ve špičatých závorkách) dva generické typy, první udává datový typ klíče a druhý typ hodnoty. Postupně přiřadíme dvě hodnoty. |
2. |
|
O |
String |
Metoda vrátí hodnotu uloženou s klíčem |
3. |
|
null |
? |
Metoda se pokusí najít hodnotu s klíčem |
4. |
|
2 |
int |
Metoda vrací aktuální počet přiřazených prvků. |
5. |
|
O, K |
Object[] |
Tento výraz poskytuje pole všech přiřazených klíčů. |
6. |
|
O |
String |
Metoda odstraní hodnotu uloženou s klíčem |
Podobně jako v případě listu, můžeme do mapy zapisovat i nedefinované hodnoty null
, klíčem však musí být vždy existující objekt.
3.13.4. Rozhraní Stream
I když se může jevit implementace třídy List<>
jako ideální nástroj pro zpracování skupinových dat, můžeme narazit na jeho limity.
Hlavním faktorem nemusí být jen omezený počet prvků seznamu, ale i značná paměťová velikost jeho obsahu.
Příkladem nevhodného použití může být hromadné zpracování fotografií, kde velikost každého obrázku může přesahovat 5 MB.
Pod pojmem „zpracování“ si představme třeba zmenšování obrázku pro tvorbu náhledů.
Pokud bychom chtěli umístit všechny obrázky do seznamu typu List
, pravděpodobně by zpracování skončilo výjimkou na nedostatek paměti v počítači.
Jak můžeme takový typ úlohy řešit lépe? Naznačme alespoň dvě cesty:
-
Do objektu typu
List
můžeme umístit pouze zástupný identifikátor obrázku (třeba cestu k fotografii na lokálním disku). -
Pro zpracování můžeme využít datový typ
Stream
, který umí odebírat data (z nějakého zdroje) postupně.
Objekt typu Stream
(česky se překládá jako proud nebo datovod) si lze představit jako potrubí na datové objekty, do kterého můžeme připojovat (postupně) různé služby pro zpracování protékajících dat, mohou to být třeba filtry nebo funkce umožňující i změnu datového typu.
Instalace takových služeb se provádí vkládáním servisních objektů (do Streamu), které pak požadovanou službu realizují.
Zmíněné servisní objekty jsou nuceny implementovat rozhraní (interface) obvykle s právě jednou abstraktní metodou tak, aby pro jejich implementaci šlo využívat Lambda výrazy zmíněné dříve.
Typ servisních metod určuje argument metody Streamu, který službu připojuje.
Objekt typu Stream
data obsahovat nemusí, jeho přínosem je především poskytování programového rozhraní (API) pro dávkové zpracování dat, což lze využít i pro úsporu paměti.
Do zdrojového kódu přináší Streamy stručnější a zpravidla lépe organizovaný zápis – zejména ve spojení s Lambda výrazy.
Čtení zdrojového kódu Streamů však nemusí být pro začátečníka jednoduché, a z toho důvodu si tady ukážeme pouze jednoduché využití tohoto datového typu.
List<Integer> numbers (1)
= Stream.of(9, null, 2, 5, 1) (2)
.filter((Integer i) -> i != null) (3)
.map(i -> i{nbsp}* 10) (4)
.sorted() (5)
.limit(2) (6)
.collect(Collectors.toList()) (7)
; (8)
1 | Výsledek zpracování očekáváme jako List celých čísel. |
2 | Vytvoříme nový objekt typu Stream tovární metodou of() , která umožňuje vložit datové prvky argumentem metody. |
3 | Pro další zpracování filtrujeme pouze definovaná čísla (různá od null ).
Pro stručnější zápis třídy filtru využijeme Lambda výraz, kde potřebný algoritmus uvedeme vpravo za šipkou -> .
Argument metody (vlevo od šipky) nemusí (v tomto případě) obsahovat explicitní definici datového typu, a tento argument pak nemusí být ani obalen v kulatých závorkách, jak si ukážeme na dalším řádku. |
4 | Metoda map() vkládá do Streamu objekt implementující funkci.
Implementace naší funkce vrací hodnotu parametru vynásobenou konstantou 10 . |
5 | Metoda sorted() provádí řazení hodnot od nejmenší k největší, pro jiné řazení je třeba dodat potřebný komparátor. |
6 | Výsledný datový proud omezíme jen na první dvě čísla. |
7 | Uvedený agregační výraz zkonvertuje datový proud na seznam čísel typu List<Integer> . |
8 | Celý výraz má formu jediného Java příkazu_ (zakončeného jediným středníkem).
Pokud vás zajímá výsledek uložený do proměnné numbers , tak ten bude obsahovat dvě čísla: 10 a 20 .
Třída Stream nabízí také API poskytující součet či aritmetický průměr výsledků, možností je hodně. |
Praktické použití třídy Stream
si lze prohlédnout a vyzkoušet v kapitole Programové výrazy.
Je dobré o existenci třídy Stream
vědět, ale pochopit správně celou její koncepci může zabrat nějaký čas, a tak řešené ukázky praktických příkladů (k této knize) tuto třídu neobsahují.
Pro zájemce o další studium přikládám internetový odkaz [14].
3.14. Webové technologie a pojmy
Na chvíli se vzdálíme od programovacího jazyka Java, všechny nové pojmy pak brzy zhodnotíme v reálných příkladech.
Podívejme se ale nejdřív na zjednodušené fungování webových aplikací a zkusme si představit běžného čtenáře webových stránek, který v internetovém prohlížeči klikne na nějaký odkaz. Internetový prohlížeč je aplikace, která primárně zobrazuje webové stránky v graficky pěkné podobě. Nejznámější produkty jsou: Chrome, Firefox, Internet Explorer, existuje však celá řada dalších. Webový odkaz si pak můžeme představit jako nějakou adresu (jiné) webové stránky na internetu, která se označuje běžně zkratkou URL (je odvozena z anglického termínu: Uniform Resource Locator). Při troše zjednodušení můžeme říci, že internetový prohlížeč předá prostřednictvím internetové sítě žádost webovému serveru o získání nějaké webové stránky. Webový server tuto žádost posoudí a vrátí zpět buď stránku očekávanou, nebo i nějakou nestandardní, která může obsahovat třeba zprávu o chybě. Taková komunikace využívá síťovou architekturu, která se označuje pojmem klient-sever, a je pro ni typické, že klient žádá (prostřednictvím počítačové sítě) službu nějakého serveru a čeká na odpověď. Popsanou architekturu zobrazuje následující schéma:

Dodejme, že na straně serveru je celá řada možností sestavení té webové stránky, pro potřeby této knihy budeme využívat prostředky programovacího jazyka Java.
3.14.1. XML – rozšiřitelný značkovací jazyk
Jazyk XML (z anglického Extensible Markup Language) nabízí sadu pravidel umožňujících zápis stromové struktury dat
v textovém formátu.
Jazyk se využívá nejen při tvorbě webových aplikací: pro svoji dobrou čitelnost a podporu mnoha knihoven (napříč technologiemi) našel řadu uplatnění i v dalších oblastech při zpracování dat.
Pro popis uzlů stromu se používají XML elementy, které se zapisují v textu párovými značkami obsahujícími jméno elementu ohraničené špičatými závorkami.
Pojem element označuje uzel stromu včetně všech jeho dětských uzlů (přesněji elementů). |
S touto znalostí můžeme zkusit přepsat část struktury jabloně (z kapitoly Stromová struktura dat) do formátu XML, výsledek je zde:
<?xml version="1.0" encoding="UTF-8"?> (1)
<a> (2)
<b> (3)
<leaf/>
<apple/> (4)
<c></c>
<d>
<leaf/>
</d>
</b> (3)
<e>
<f>
<leaf/>
<g></g>
<h>
<leaf color="green"/> (5)
<leaf color="yellow"/>
</h>
</f>
</e>
<!-- Tady mohou být popsány další větve stromu. --> (6)
</a>
1 | První řádek obsahuje hlavičku XML souboru s kódovou stránkou obsahu a verzí značkovacího jazyka. |
2 | Kořen stromu je popsán párem značek <a> – </a> , ukončovací značka obsahuje lomítko před názvem elementu. |
3 | Následuje vnořený element popsaný párovými značkami <b> – </b> .
Názvy reálných elementů bývají většinou určitá slova (či zkratky), více slov se běžně spojuje notací CamelCase. |
4 | Pokud se jedná o list stromu (uzel bez dalšího větvení), je možné závěrečnou značku vynechat, jen lomítko se přesune před závorku uzavírající první značku podle vzoru <apple/> . |
5 | Příklad zápisu atributu, kde výraz color označuje název a green jeho hodnotu.
Jeden element může mít i více atributů, jejich hodnoty se uvádí v uvozovkách (jednoduchých či dvojitých).
V případě párového elementu se atributy uvádí u prvního tagu. |
6 | XML dokument může obsahovat běžný text umístěný mezi elementy a může obsahovat také komentáře, které se při zpracování budou ignorovat. Komentáře nelze vnořovat do sebe. |
Všechny elementy v jednom uzlu mohou mít shodný název – na rozdíl od tříd z jednoho balíčku.
Každý element může obsahovat atributy, které se zapisují za název elementu (prvního z páru) v pořadí: název, rovnítko, hodnota v uvozovkách (jednoduchých či dvojitých), viz příklad: <apple color="red"/>
.
Bílé znaky (mezery, tabulátor, odřádkování) umístěné mezi elementy jsou nevýznamné, a tak celý XML soubor může být zapsán na jediném řádku, pro lepší čitelnost se však každá nová úroveň elementů odsazuje (třeba mezerami). Prostor mezi elementy lze prokládat textem, pokud to má při zpracování smysl. Dokument formátu XML má několik speciálních znaků pro zápis (a čtení), které vyžadují zvýšenou pozornost, jejich popis je zde:
-
Špičaté závorky (
<
a>
) jsou určené výhradně pro značkování; pokud je třeba zapsat je do textu, použije se náhradní výraz (<
a>
). -
Ze stejného důvodu je znak
&
určen výhradně pro zápis speciálních znaků; pokud je třeba zapsat ho do textu, použije se náhradní výraz (&
). -
Aby nedošlo k záměně uvozovky v hodnotě atributu s jeho ohraničením, lze dvojité uvozovky nahradit výrazem
"
a jednoduché výrazem'
. Alternativou je použít pro ohraničení hodnoty parametru jiné uvozovky, než obsahuje jeho tělo, například:<apple character='"'/>
.
Pokud dokument splňuje vše uvedené, říkáme, že je správně strukturovaný či syntaticky správný (anglicky well-formed). |
Pro potřeby strojového zpracování však často potřebujeme pevná pravidla obsahující seznam povolených elementů, jejich atributů (včetně datových typů) a také struktury celého dokumentu. Definice takových pravidel se dnes běžně popisují souborem s příponou XSD (z anglického XML Schema Definition), což je opět dokument formátu XML sestavený podle určitých pravidel.
Pomocí XSD souboru lze ověřit platnost XML dokumentu. Pokud jeho struktura splňuje požadavky XSD, říkáme, že XML dokument je platný či validní (anglicky valid). |
Kontrola platnosti XML proti XSD dokumentu se využívá běžně před zahájením strojového zpracování (pomocí programových knihoven), v případech neshody pak dochází k přerušení dalšího zpracování dat. Pro kontrolu platnosti ručně sestaveného XML souboru lze vyžít nějaký specializovaný editor, který bývá označován zkratkou IDE (z anglického Integrated Development Environment).
Zbývá doplnit, že elementy mohou mít svůj jmenný prostor, který umožňuje sestavit XML soubor podle pravidel více XSD souborů. V této knize se jmenným prostorem pracovat (přímo) nebudeme, a nebudeme potřebovat ani žádné vlastní definice typu XSD. K tomuto pojmu se však ještě vrátíme v následující kapitole.
3.14.2. HTML – hypertextový značkovací jazyk
HTML jazyk (z anglického Hypertext Markup Language) se používá jako datový zdroj pro grafickou prezentaci informací v prostředí webového prohlížeče. Obsah souboru ve formátu HTML si můžeme představit jako XML soubor z předchozí kapitoly, který splňuje definici jazyka HTML popsanou ve formátu XSD, v takovém případě hovoříme o specifikaci jazyka XHTML. Výhodou takového pojetí je snadné využití standardních nástrojů XML dokumentů, jako jsou editory, validátory kódu a různé knihovny pro strojové zpracování dokumentu. Nevýhodou však jsou problémy způsobené různou mírou tolerance chyb internetových prohlížečů, protože některé prohlížeče zobrazují přijatelně i nevalidní XHTML, zatímco jiné prohlížeče mohou chybný kód interpretovat jinak, což uživatelé vnímají negativně bez ohledu na původ chyby.
Z toho důvodu se dnes více ujal novější formát HTML5, který akceptuje i různé odchylky proti XSD, a tím zlepšuje vzájemnou kompatibilitu internetových prohlížečů. Specifikace HTML5 nabízí také některé nové funkce (kreslení, přetažení a přehrávání videa) a umožňuje stručnější zápis hlavičky dokumentu. Vývojáři stránek HTML5 však mají i nadále k dispozici formát podporující XSD jako přísnější alternativu.
Ukázky v této knize mají sice správně párované značky elementů, přesto však využívají volnější specifikaci jazyka HTML5. Podívejme se na ukázku…
<!DOCTYPE html> (1)
<html lang="en"> (2)
<head> (3)
<meta charset="UTF-8"/>
<title>Demo</title>
<link href="css/basic.css" rel="stylesheet"/>
</head>
<body> (4)
<h1>Hello, World!</h1> (5)
</body>
</html> (2)
1 | První řádek obsahuje typ dokumentu a jeho obsah se od XML formátu liší. |
2 | Následuje kořenový element stromu s názvem html s (volitelným) atributem lang pro označení jazyka stránky, jehož hodnota se uvádí v kódu ISO [3].
Kořenový element obsahuje ještě dva potomky. |
3 | head – Hlavička obsahuje spíše technické informace o znakové sadě celého dokumentu, titulek okna internetového prohlížeče a odkaz na soubor kaskádových stylů (CSS, budeme se mu věnovat krátce v následující kapitole). |
4 | body – Tato část obsahuje tělo zprávy, která se prezentuje v internetovém prohlížeči. |
5 | V tomto případě obsahuje jediný element h1 (z angličtiny: heading level 1), který reprezentuje nadpis nejvyšší úrovně. |

Začíná být zřejmé, že při tvorbě HTML stránky se bez popisu základních elementů neobejdeme, podívejme tedy na několik nejpoužívanějších:
-
h1
(heading level 1) – jak jsme už zmínili, jedná se o nadpis první úrovně, přitom úroveň nadpisu lze zvyšovat až do velikosti 6, -
p
(paragraph) – odstavec běžného textu, -
div
(division) – ohraničí obdélníkovou oblast uvnitř HTML stránky, -
span
– ohraničí část textu na řádku, -
a
(anchor) – element zvaný odkaz označuje část textu, která po kliknutí myší vede k načtení nové HTML stránky, -
table
– tělo tabulky, které obsahuje elementy řádků (tr
) a jednotlivé buňky (td
), které pak tvoří sloupce, -
form
– formulář je neviditelná obálka grafických komponent umožňujících vkládání dat pomocí klávesnice. Typickým obsahem formuláře je následující element: -
input
– element umožňující zápis dat (pomocí klávesnice) určených k odeslání na server.
Pro další studium lze použít třeba tento odkaz [20]. Je obecně doporučované, aby HTML kód tvořil především formální strukturu dokumentu, a výsledný grafický vzhled se dotváří spíše prostředky kaskádových stylů (CSS).
3.14.3. CSS – Kaskádové styly
Kaskádové styly (anglicky Cascading Style Sheets) popisují, jak se mají HTML elementy zobrazovat. Často se jedná o velikost, barvu a tvar písma, rámečků, obrázků a dalších grafických objektů zobrazených na obrazovce nebo i v jiných médiích (tiskárna).
Kaskádové styly lze s výhodou umístit do samostatného souboru, v HTML kódu se pak uvádí jen reference přes element hlavičky link
…
<link href="css/basic.css" rel="stylesheet"/>
Většina HTML elementů nabízí parametr class
(nezaměňovat s pojmem Java class), jehož hodnoty se využívají při párování grafických stylů definovaných v souborech CSS.
U nového webového projektu se vyplatí začít výběrem vhodné CSS knihovny a v HTML kódu pak používat především názvy stylů z této knihovny. Pro začátek můžeme zkusit třeba populární knihovnu Bootstrap [25]. Vlastními CSS styly pak lze realizovat jen specifické požadavky na grafický vzhled. |
Hlavička HTML stránky může obsahovat postupně odkazy i na několik různých CSS souborů. Pokud mají dva styly stejnou váhu, internetový prohlížeč použije vždy ten později definovaný styl. Použití CSS je však téma na samostatnou knihu a způsob jejich použití není zásadní pro porozumění obsahu této knihy. Styly dostupných HTML stránek lze studovat pomocí nástrojů internetového prohlížeče pro vývojáře. V prohlížeči Firefox zkusme otevřít první ukázku k této knize: levým tlačítkem klikněte na text nadpisu, pak vyskočí kontextové menu a v něm vyberme volbu Prozkoumat. Měli bychom spatřit obrazovku podobnou této:

Na pravé straně vidíte název barvy elementu h1
, v tomto režimu lze styly také (dočasně) upravovat.
Výchozí stránku obnovíte stiskem klávesy F5
.
V této knize se zaměříme především na strukturu HTML kódu a s dosavadními informacemi bychom si měli vystačit i v následujících příkladech.
Pro další studium můžete použít třeba tento odkaz [21].
3.14.4. URL – Adresa internetové stránky
Pojem URL (z anglického Uniform Resource Locator) je jednořádkový řetězec, který slouží k adresování webových zdrojů, mezi něž patří zejména HTML stránky, CSS styly a obrázky. V URL je možné najít určitou podobnost s adresováním souborů na disku lokálního počítače, URL však podporuje některé další části, například parametry. Většina internetových prohlížečů zobrazuje aktuální URL ve své horní části, kde mají uživatelé možnost ji změnit. Velká a malá písmena mohou být významná. Na příkladu konkrétního URL si popíšeme důležité části, přitom je dobré vědět, že některé z nich jsou nepovinné.
https://stackoverflow.com:443/search?q=java
-
https
– Označuje název komunikačního protokolu pro bezpečnou výměnu dat v počítačové síti pomocí protokolu SSL. Nezabezpečený protokol má kód http, pro čtení souborů z lokálního disku lze použít protokol file. -
://
– Oddělovač protokolu. -
stackoverflow.com
– Doména druhého a prvního řádu se odděluje tečkou a společně definují jedinečný název hostitelského počítače. Tento název se převádí pomocí DNS serverů na jedinečný identifikátor hostitelského počítače zvaný IP adresa (z anglického Internet Protocol address). Při sestavování URL adresy lze použít také IP adresu, ale častěji se používají doménová jména. Pro oddělení případné domény třetího řádu a dalších se používá rovněž tečka. -
/
– Každé další lomítko odděluje popis detailního umístění zdroje na serveru. -
:443
– Číslo portu, lze si ho představit jako komunikační kanál serveru. Pokud číslo portu chybí, server použije nějaké výchozí číslo podle své konfigurace. -
search
– Specifikace detailního umístění na serveru může někdy sloužit i jako doplňkový parametr (příkaz). -
?
– Oddělovač parametrů. -
q=java
– Zápis URL parametru, kdeq
je jeho název ajava
je jeho hodnota. Pokud URL obsahuje více parametrů, oddělují se znakem&
.
Připomeňme si, že HTML stránky nabízí elementy typu odkaz umožňující načtení jiné HTML stránky pomocí adresy zvané URL
.
Adresování může být absolutní (obsahující celou URL), nebo relativní k aktuální HTML stránce.
Stránku na stejné adrese lze odkazovat pouhým názvem (s volitelnými parametry).
To je také případ adresování CSS souboru v předchozí kapitole pomocí parametru href
.
3.15. Shrnutí
Pod pojmem programový objekt rozumíme instanci třídy (ve významu datového typu), která je definována svým názvem a balíčkem. Instance pak vzniká za běhu aplikace v paměti počítače. Programový objekt se skládá z atributů (které obsahují data) a metod (které obsahují algoritmy). Každá metoda může (ale nemusí) deklarovat své parametry, pomocí nichž se do metody dostávají data z volající třídy, a také může (ale nemusí) vracet zprávu pomocí právě jednoho datového typu. V jazyce Java rozeznáváme primitivní datové typy, třídy anebo pole. Hodnoty datových typů lze použít v definici atributu objektu, parametru metody, návratové hodnoty metody, anebo na určení typu lokální proměnné. Metody objektů se dědí z rodiče objektu (i jeho prarodičů), pokud to dovoluje jejich viditelnost. Společným předkem všech objektů jazyka Java je třída zvaná Objekt. Na rozdíl od metod se konstruktory nedědí. Objekt může (ale nemusí) obsahovat statické atributy a statické metody (také se nedědí), které se volají pomocí reference třídy (nikoli pomocí reference objektu). Statické atributy se využívají běžně jako konstanty. Dědičnost je vlastnost třídy, která umožňuje redukovat duplicitní zdrojový kód a zároveň umožňuje třídy sdružovat do větších logických celků.
Internetový prohlížeč zahajuje komunikaci se serverem pomocí protokolu HTTP a pro vykreslení běžných webových stránek dostává text ve formátu jazyka HTML, který následně vykreslí klientovi. Vedle HTML kódu umí internetový prohlížeč zpracovat CSS soubory popisující grafické vlastnosti HTML elementů a obrázků, úplný výčet služeb internetového prohlížeče je dokumentován několika specifikacemi.
Pojďme se podívat na reálné příklady.
4. Řešené příklady
Žijete jenom jednou. Tak by to měla být zábava.
francouzská módní návrhářka
Ve druhé části knihy najdeme výklad vybraných částí zdrojového kódu z přiložených příkladů [2], které jsou uspořádány ve formátu projektu Maven, jejich zdrojový kód lze používat v souladu s licencí deklarovanou výše. Každá z kapitol začíná stručným zadáním úkolu a pokračuje popisem programového řešení, ke stejnému cíli však mohou vést i postupy jiné. Závěrem kapitoly jsou uvedeny tipy pro další úpravy či rozšíření. Kapitoly jsou řazené tak, aby získané dovednosti na sebe navzájem navazovaly, předpokládá se přitom znalost pojmů uvedených v první části knihy. Po přečtení kapitoly doporučuji experimentovat se staženým zdrojovým kódem projektu a pozorovat dopad vlastních změn na grafické rozhraní. Pro obnovu původního řešení stačí smazat kořenový adresář projektu a zkopírovat soubory ze staženého ZIP archivu znovu do adresáře.
Jak bylo zmíněno úvodem knihy, jednotlivé příklady lze zobrazovat (po spuštění projektu) v internetovém prohlížeči jako HTML stránky. V patičce každé takové stránky najdeme odkazy pro přepínání se mezi webovou aplikací a jejím zdrojovým kódem, s možností přechodu na hlavní nabídku příkladů.

Vhodné vývojové prostředí přijde vhod při vlastních úpravách či tvorbě nového kódu.
4.1. Instalace a spuštění
Běh ukázkových příkladů byl testován na OpenJDK 11 LTS, což je Java s otevřenou licencí [4] a dlouhodobou podporou (anglicky Long Term Support), testy však probíhaly také na OpenJDK 8 a 14. Při operačním systému Windows 10 byl použit virtuální počítač s pamětí 3 GB RAM s kapacitou disku 126 GB, Linux Xubuntu 20.04 LTS běžel na virtuálním počítači s pamětí 1 GB RAM a kapacitou disku 8 GB. Zdrojový kód projektu byl vytvořen v prostředí integrovaného editoru NetBeans [8].
Další postup vysvětlíme na OpenJDK 11, instalace jiné Javy může být analogická, jinak postupujte podle pokynů dodavatele.
4.1.1. Struktura projektu
Zdrojové soubory ukázkových příkladů jsou uloženy ve struktuře vhodné pro zpracování nástrojem Maven.
Projekt typu Maven je tvořen souborem pom.xml
, textovými soubory s příponou .java
a případně dalšími soubory, jako jsou obrázky či další textové soubory různých formátů.
Pro spuštění projektu využijeme příkaz nástroje Maven, který projekt sestaví pomocí celé řady dílčích kroků:
-
Zkontroluje instalovanou verzi Java Development Kit (JDK).
-
Zkontroluje strukturu projektu a přečte konfigurační soubor
pom.xml
. -
Při prvním sestavení (či spuštění) Java projektu si Maven stáhne (z centrálního repozitáře) potřebné knihovny (obecně se jim říká závislosti, anglicky dependences) na pevný disk do podadresářů (podsložek)
.m2
v našem domovském adresáři. Opakované volání příkazů Mavenu je už rychlejší, protože se stahují jen chybějící knihovny. Při opakovaném sestavování projektu se používají knihovny stažené dříve, adresář (složku) s knihovnami najdeme po prvním spuštění projektu v domovském adresáři (domovské složce) uživatele s názvem.m2
(úvodní tečka v názvu bývá někdy symbolem skrytého adresáře). -
Provede kompilaci zdrojového kódu.
-
Výsledné soubory sestaví do souboru vhodného k další distribuci, v našem případě bude mít soubor příponu
war
(odvozeno z anglického web archive).
4.1.2. Znakový terminál
Příkazy nástroje Maven budeme vkládat prostřednictvím aplikace zvané emulátor znakového terminálu.
Pojem znakový terminál představoval původně samostatné fyzické zařízení určené pro ovládání počítače pomocí klávesnice a textového černobílého monitoru.
Textové příkazy se pro ovládání počítače využívají stále, ale dnes už se vkládají pomocí SW emulátoru znakového terminálu, který má v běžných grafických operačních systémech často podobu černého okna s bílými znaky.
Místo, kam se příkazy zapisují, se označuje pojmem příkazový řádek a ten bývá označen blikajícím kurzorem, jednotlivé příkazy se potvrzují klávesou Enter
.
Potvrzený příkaz obecně nelze vrátit zpět.
Emulátor znakového terminálu lze spustit (jako každou jinou aplikaci) z menu operačního systému:
-
v systému Windows se aplikace jmenuje Příkazový řádek,
-
v systému Linux Xubuntu je uveden pod názvem Emulátor terminálu.

4.1.3. Získání příkladů
-
Stáhneme zdrojové kódy příkladů z URL adresy [2] ve formátu ZIP.
-
Soubory obsažené v archivu ZIP zkopírujeme do nové složky (na Linuxu se používá pojem adresář, anglicky directory). Název složky doporučuji zvolit raději bez mezer.
4.1.4. Instalace Java a Mavenu
-
Stáhneme si do počítače instalační binární archiv OpenJDK [4] a seznámíme se s licenčními podmínkami.
-
Archiv rozbalíme do vhodného adresáře na disku.
-
Pro sestavení projektu můžeme použít Apache Maven (dále jen Maven), který je součástí balíčku stažených příkladů, před prvním spuštěním je však třeba opravit (běžným textovým editorem) systémovou proměnnou
JAVA_HOME
tak, aby její hodnota za rovnítkem ukazovala na existující kořenový adresář, do kterého jsme Javu instalovali.Zdrojový kód 23. Upravený řádek souboru .\mvnw.cmd ve Windowsset "JAVA_HOME=C:\Users\User\Documents\Java\jdk-11"
Pokud jsme Java archiv rozbalili do kořenového adresáře našeho Maven projektu, můžeme použít i relativní cestu (například jen: jdk-11
).
Uživatelé Linuxu budou editovat soubor mvnw
.
Pro jiný postup instalace doporučuji řídit se instrukcemi výrobce.
4.1.5. Instalace Mavenu na Linux
Uživatelé Linuxu Xubuntu mohou také použít výše uvedený postup, anebo instalovat produkty z centrálního repozitáře příkazem:
sudo apt install maven
Program si vyžádá pouze heslo k oprávnění instalace do systému, a pak nainstaluje Maven včetně závislosti na OpenJDK [4].
4.1.6. Spouštění příkladů
V textové konzoli se přepněte do adresáře projektu a ve Windows spusťte příkazy:
-
.\mvnw clean install
-
.\mvnw -f Samples/pom.xml jetty:run
Na Linuxu je to velmi podobné, pouze úvodní lomítka budou opačná (./mvnw
).
Pokud je Maven v systému standardně instalovaný, příkaz se ještě zjednoduší na mvn
(bez označení adresáře).

Zbývá spustit váš oblíbený internetový prohlížeč (například z menu operačního systému) a vložit do adresního řádku text http://localhost:8080/
.
Po otevření stránky by se měla zobrazit nabídka příkladů.
Aplikaci lze ukončit v konzoli (terminálu) klávesami Ctrl-C
, k ukončení však dojde také zavřením okna konzole (křížkem vpravo nahoře).
V kořenovém adresáři projektu lze alternativně použít skript mvnw
(místo mvn
), který zavolá Maven stažený s projektem (za podmínky, že najde instalaci JDK).
4.1.7. Co dělat při potížích?
Pokud vám internetový prohlížeč ukáže stránku odpovídající výše uvedenému obrázku, zbytek této kapitoly lze přeskočit. V opačném případě zvažte následující možnosti:
-
Zkontrolujeme, zdali je Maven správně nainstalovaný: otevřeme si znakový terminál pro vkládání příkazového řádku a napišme příkaz:
mvn --version
Měli bychom spatřit zprávu obsahující verzi Mavenu a JDK podobnou té z následujícího obrázku, která pochází z Linuxu:
Obrázek 16. Výpis verze nástroje Maven v Linuxu -
Zkontrolujme dostatek volného místa na pevném disku: pokud tam zbývá méně než 100 MB, smažeme nepotřebné soubory a postup spuštění opakujeme.
-
Ověřme dostatek volné operační paměti a ostatní požadavky popsané v kapitole Co budeme potřebovat a ukončeme všechny nepotřebné aplikace v počítači.
-
Prozkoumejme chybovou zprávu příkazu, který selhává. Pro podrobnější informace zkusme vložit klíčová slova chybové zprávy do internetového vyhledávače.
-
Někdy se můžeme setkat se zprávou, která vzniká v důsledku obsazení čísla portu jinou aplikací:
Failure: Failed to bind to 0.0.0.0/0.0.0.0:8080: Adresa je užívána -> [Help 1]
V tomto případě máte jednu ze dvou možností:
-
Najít aplikaci, která port obsadila, a ukončit ji.
-
V souboru
Samples/pom.xml
vyhledat číslo8080
a zkusit ho změnit (třeba na hodnotu8090
).
-
-
Na domovské stránce knihy [1] zkusme hledat řešení mezi často kladenými otázkami, pomoci může také instruktážní video.
4.1.8. Virtuální počítač
Pokud jsme vyčerpali výše uvedené rady a projekt stále neběží, můžeme (v případě silnějšího počítače) zkusit nainstalovat aplikaci pro tvorbu virtuálních počítačů zvanou VirtualBox, do které lze pak nainstalovat čistý Linux Xubuntu [17]. Právě v takovém prostředí byl výše uvedený postup opakovaně testován bez komplikací. Virtuální počítač běží v samostatném okně a výsledek může vypadat takto:

Z pohledu bezpečnosti procesy virtuálního počítače nijak neovlivňují původní hostitelský počítač a po jeho odstranění dojde také k odstranění celého jeho souborového systému. Při použití virtuálního počítače je třeba počítat vždy se ztrátou výkonu a s požadavkem dodavatele systému na platnou licenci. Protože instalace operačního systému do virtuálního počítače je však poměrně zdlouhavá a náš počítač nemusí vždy disponovat potřebným výkonem, zmíníme ještě krátce spuštění projektu ve virtualizačním kontejneru.
4.1.9. Virtualizační kontejner
Virtualizační kontejner si lze představit jako lehký virtuální počítač, který běží (na našem počítači) s minimálními nároky na zdroje počítače, kde dochází ke spuštění prakticky okamžitě.
Kontejner nemívá zpravidla grafické prostředí a ovládá se textovými příkazy – podobně jako v běžném znakovém terminálu.
Programové vybavení kontejneru se zavádí pomocí příkazů uvedených v souboru Dockerfile
.
Pro kontejner je výhodné použít operační systém s otevřenou licencí.
Tyto kontejnery nabývají na popularitě díky vysokému výkonu, dobré automatizovanosti, přenositelnosti aplikací a vlastnostem umožňujícím efektivnější využívání stávajících systémových prostředků.
Spuštění našeho Java projektu v takovém kontejneru si můžeme vyzkoušet v aplikaci zvané Docker® [28]. Uživatelům systému Windows doporučuji ověřit nejdříve splnění technických požadavků na instalaci a po stažení instalačního programu ze stránek dodavatele pak postupovat doporučeným způsobem. Náš projekt pak spustíme (ze znakového terminálu) následujícím skriptem…
bin\docker-run.cmd
Uživatelé operačního systém Ubuntu (či jiné linuxové distribuce založené na systému Debian) spustí projekt skriptem, který chybějící Docker umí automaticky doinstalovat. Pro spuštění je třeba mít (v operačním systému) administrátorské oprávnění.
sh bin/docker-run.sh
Spuštěný skript pak provede přibližně následující činnosti:
-
Podle obsahu souboru
Dockerfile
založí nový linuxový [29] virtualizační kontejner s Javou (OpenJDK) verze 8. -
Nakopíruje tam náš Maven projekt.
-
Spustí skript
bin/run-example.sh
, který náš projekt zkompiluje a spustí jako webovou aplikaci. -
Webové stránky otevřeme (v internetovém prohlížeči našeho počítače) na adrese
localhost:8080
. -
Ukončení webové aplikace provedeme stiskem kláves
Ctrl-C
.
Popis dalších vlastností a řešení případných problémů spojených s použitím virtualizačního kontejneru přesahují bohužel rámec této knihy, pro další informace doporučuji využít dokumentaci na stránkách jeho výrobce [28].
4.1.10. Vývojové prostředí
Na vývoj aplikací se běžně používá grafické rozhraní, které nabízí editor s podporou doplňování kódu a s jeho barevným zvýrazňováním, dále integrovaný kompilátor, podporu spuštění aplikace, nástroje na vyhledávání chyb a krokování běžícího programu, a k tomu celou řadu dalších užitečných služeb. Aplikacím tohoto typu se říká integrované vývojové prostředí (anglicky Integrated Development Environment) a často se označuje zkratkou IDE. Podívejme se na některá nejpoužívanější, která lze použít bez licenčních poplatků i pro komerční účely. Každé z nich je multiplatformní (to znamená, že na různých operačních systémech se ovládá prakticky stejně), nabízí barevné zvýrazňování kódu či podporu doplňování zdrojového kódu, a každé má své příznivce na malých i velkých projektech.
-
Apache NetBeans® je projekt, který vzešel z českého studentského projektu a aktuálně je ve správě neziskové organizace Apache Software Foundation [25]. Výchozí instalace obsahuje vše potřebné i pro běžného začátečníka, výhodou je intuitivní ovládání a těsná integrace s projekty typu Maven.
-
IntelliJ IDEA® nabízí (vedle placené verze) také verzi komunitní, kterou lze užívat bez poplatku. Jeho ovládání je dobře zdokumentované, stažením pluginů lze získat nejrůznější rozšíření. Grafické prostředí bylo postavené čistě na knihovně Java Swing podobně, jako v případě předchozího produktu.
-
Eclipse® patří též do skupiny open-source projektů s dostatečným komfortem ovládání. Produkt je napsaný v jazyce Java, ale pro vykreslování grafických prvků používá nativní knihovny operačního systému – prostřednictvím frameworku Standard Widget Toolkit (SWT).
Popis otevření a spuštění projektu ve vývojovém prostředí přesahuje rámec knihy, zájemcům doporučuji vyhledat na internetu vhodný návod pro vybraný produkt.
4.2. Servlety
Pojmem servlet označujeme v této knize jako potomka třídy HttpServlet, která poskytuje metody pro zpracování internetového dotazu (anglicky HTTP request) a odeslání textové zprávy ve formátu HTML zpátky do internetového prohlížeče (anglicky HTTP response). Pro správné fungování servletů potřebujeme spustit ještě nějakou aplikaci, která umí takový požadavek přijmout a podle nějakých pravidel delegovat jeho zpracování na instanci konkrétního servletu. Pro platformu Java nabízí takovou službu webový server, který poskytuje také celou řadu dalších služeb. Protože v našich příkladech potřebujeme jen základní služby, bude nám stačit zjednodušená implementace webového serveru, které se říká webový kontejner. My použijeme konkrétní produkt s názvem Jetty®. Podívejme se teď na jednoduchou webovou komunikaci:
-
Uživatel zapíše do internetového prohlížeče konkrétní URL, který obsahuje zpravidla úplnou adresu HTML stránky s případnými parametry.
-
Takový požadavek (anglicky request) odešle prohlížeč metodou typu GET na server.
-
Webový server (či kontejner) zprávu přijme a vyhledá vhodný servlet pro další zpracování, přitom jeho třídu si odvodí z URL – z části popisující konkrétní HTML stránku, za chvíli to popíšeme přesněji.
-
Na tomto objektu zavolá metodu
doGet()
a prostřednictvím parametrů mu poskytne objekt typuPrintWriter
určený pro zápis výsledné stránky. Tento objekt si lze představit jako nějaký „trychtýř“ napojený na dlouhé „potrubí“, kterým bude server posílat části textů zpět do internetového prohlížeče. Ve skutečnosti probíhá komunikace se vzdáleným počítačem přes nějaký datový kabel či bezdrátově a využívány přitom jsou různé technologie a komunikační protokoly. Naše znalosti však mohou končit na úrovni programového rozhraní programových objektů, protože potřebné služby zajistí Java knihovny, operační systém, síťová karta našeho počítače a další sofistikované technologie na cestě ke vzdálenému počítači. Při zkoušení našich příkladů nám služby vzdáleného serveru napodobí náš počítač, a tak internetové připojení bude potřeba pouze před spouštěním aplikace pro stažení potřebných Java knihoven.

Výše uvedený obrázek znázorňuje schematicky komunikaci klientské Java aplikace se vzdáleným serverem.
-
Internetový prohlížeč klienta odešle požadavek na konkrétní webový server prostřednictvím internetové sítě.
-
Server požadavek zpracuje a výsledek pošle zpět ve formátu textové HTML stránky.
-
Po odeslání celé části textu „trychtýř“ vyprázdní metodou
close()
. -
Internetový prohlížeč zprávu zpracuje (parsuje) a jeho grafickou podobu zobrazí uživateli na monitoru počítače.
4.2.1. Hello, World!
Prvním naším úkolem bude vypsat (prostředky jazyka Java) jednoduchý text „Hello, World!“ do internetového prohlížeče ve formátu HTML, jeho kód by mohl vypadat takto…
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'/>
<title>Demo</title>
<link href='css/basic.css' rel='stylesheet'/>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
Pro nás to znamená vytvořit třídu (potomka třídy HttpServlet
) s překrytou metodou doGet(request, response)
, která bude zapisovat HTML kód (uvedený výše) do internetového prohlížeče – prostřednictvím objektu PrintWriter
získaného z argumentu response
.
Nová třída se může jmenovat třeba HelloWorldServlet
, podívejme se na diagram tříd:
V diagramu vidíme dva zeleně vyznačené datové typy, které třída HttpServlet
používá (anglicky use) v metodě doGet()
, jinými slovy lze také říct, že třída HttpServlet
je na nich závislá.
Jeden typ reprezentuje žádost o zpracování (anglicky request) a druhý jeho odpověď (anglicky response).
Jejich vztah k servletu je vyznačen přerušovanou čárou zakončenou otevřenou šipkou.
Nové typy se nazývají rozhraní (anglicky interface), a tímto anglickým výrazem se označuje také hlavička (stereotyp) grafického prvku.
Interface prakticky
Připomeňme si krátce, že rozhraní interface představuje popis metod objektů (včetně parametrů a návratových typů). Aby mohl vzniknout objekt typu tohoto rozhraní, je třeba vytvořit nejdříve třídu, která bude abstraktní metody (toho rozhraní) implementovat. Základní informace byly uvedeny v kapitole Interface. Pro naše řešení budeme potřebovat dvě rozhraní z diagramu:
-
HttpServletRequest
nabízí metody, pomocí kterých lze získat parametry URL dotazu (anglicky request). -
HttpServletResponse
nabízí také několik metod, pro náš úkol je nejdůležitější metoda poskytující instanci typuPrintWriter
, pomocí které budeme zapisovat texty určené do internetového prohlížeče.
První servlet
Implementaci prvního servletu si zde představíme s jeho celým zdrojem, v dalších ukázkách se zaměříme jen na odlišnosti.
/* License terms */ (1)
package net.ponec.jbook.s01_hello; (2)
import java.io.IOException; (3)
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import net.ponec.jbook.tools.WebTools;
/** A simple servlet implementation */ (4)
@WebServlet("/helloWorld") (5)
public class HelloWorldServlet extends HttpServlet { (6)
/** Handles the HTTP GET method */ (7)
@Override (8)
protected void doGet(HttpServletRequest request,
HttpServletResponse response) (9)
throws ServletException, IOException { (10)
String html = String.join("\n", (11)
"<!DOCTYPE html>",
"<html lang='en'>",
" <head>",
" <meta charset='UTF-8'/>",
" <title>Demo</title>",
" <link href='css/basic.css' rel='stylesheet'/>",
" </head>",
" <body>",
" <h1>Hello, World!</h1>",
WebTools.createFooter(2, this), (12)
" </body>",
"</html>");
PrintWriter writer = response.getWriter(); (13)
writer.append(html); (14)
} (15)
} (16)
1 | První řádek obsahuje nepovinný komentář, obecně však může komentář zabírat i více řádků. |
2 | Druhý řádek obsahuje za klíčovým slovem package název balíčku, který musí korespondovat s fyzickým uložením třídy ve stromové struktuře adresářů. |
3 | Další skupina řádků začíná klíčovým slovem import a obsahuje seznam všech tříd a rozhraní, které jsou uváděny dále jen svým prostým jménem (to znamená bez balíčku).
Importy nejsou povinné, ale při jejich absenci by bylo nutné uvádět každou třídu včetně jejího balíčku, a zdrojový kód by se tak stal málo přehledným.
Pořadí uvedených importů je nevýznamné. |
4 | Následující tři řádky jsou nedílnou součástí deklarace třídy, na prvním z nich je zapsán (nepovinný) komentář třídy typu JavaDoc. |
5 | Anotace třídy s názvem @WebServlet obsahuje ve svém parametru umístění HTML stránky, která se stane součástí adresy URL.
Pomocí tohoto označení pak může server spárovat za běhu aplikace přijatý webový požadavek s instancí požadovaného servletu.
Pokud anotace obsahuje parametrů více, je nezbytné zapisovat je včetně jejich názvů následovaných rovnítkem.
Pokud v anotaci nepoužijeme žádný parametr, kulaté závorky anotace jsou nepovinné.
V našem případě bude URL adresa vypadat takto: http://localhost:8080/helloWorld .
Význam anotace si lze představit jako nálepku na zavařenině: nálepka nemá vliv na chuť zavařeniny, ale může usnadnit její vyhledávání v regálu. |
6 | Posledním zápisem první úrovně je vlastní název třídy HelloWorldServlet, který obsahuje deklaraci třídy začínající dvěma klíčovými slovy:
|
7 | Popis těla třídy se pro zvýraznění struktury zdrojového kódu odsazuje (zleva) počátečními mezerami. Následující čtyři řádky jsou nedílnou součástí popisu metody, začínáme komentářem ve formátu JavaDoc. |
8 | Na dalším řádku vidíme anotaci metody s názvem @Override , která deklaruje, že metoda překrývá metodu nějakého předka.
Pokud bychom udělali překlep v názvu metody, jejího parametru či návratového typu, pak díky této anotaci nás kompilátor dokáže včas upozornit na problém.
Anotace jsou nedílnou součástí deklarace metody. |
9 | Deklarace metody pokračuje dvěma klíčovými slovy:
|
10 | Klíčové slovo throws uvádí seznam výjimek oddělených čárkou, které může metoda vyhazovat.
Obě výjimky uvedené zde jsou kontrolované a jejich výčet je tedy povinný (viz kapitola Výjimky).
Kód algoritmu se zapisuje do prostoru ohraničeného párem složených závorek { a } a pro přehlednost se odsazuje mezerami. |
11 | Obsah HTML stránky sestavíme do pomocné proměnné html tak, že pospojujeme jednotlivé řádky metodou join() .
Oddělovačem prvků bude znak nového řádku, který se v jazyce Java označuje výrazem \n , přitom lomítko uvedené ve Stringovém literálu je speciální znak.
Java tímto způsobem umožňuje zápis i několika dalších speciálních znaků, zápis lomítka se provádí jeho zdvojením \\ .
Připomeňme, že znak nového řádku se projeví pouze ve zdrojovém kódu HTML stránky, na zobrazení stránky v prohlížeči to nemá vliv, jakož ani mezery pro odsazení elementů, které zapisujeme jen pro lepší čitelnost HTML kódu. |
12 | Metoda createFooter() třídy WebTools vloží do HTML stránky patičku s odkazem na zobrazení zdrojového kódu servletu a také odkaz pro návrat do hlavního menu.
Argumentem statické metody je číslo úrovně zanoření elementu a reference na aktuální instanci servletu pomocí klíčového slova this .
Zdrojový kód implementace je součástí přiloženého projektu, ale jeho implementace nebude předmětem našeho studia. |
13 | Z argumentu response získáme metodou getWriter() objekt typu PrintWriter , který zapíšeme do proměnné writer . |
14 | Obsah proměnné html pošleme do internetového prohlížeče metodou append() na objektu PrintWriter .
Uzavření objektu PrintWriter provede služba, která tento objekt vytvořila.
Metoda doGet() nemá žádný návratový typ a náš zápis algoritmu tím může skončit. |
15 | Závorka ukončující tělo metody doGet() . |
16 | Závorka ukončující tělo třídy HelloWorldServlet . |
Jakmile internetový prohlížeč obdrží celou zprávu ze servletu, vykreslí uživateli následující obraz, a my můžeme oslavit první vyřešené zadání.

Závěrem proveďme ve zdrojovém kódu vlastní změny: upravme text pozdravu tak, aby obsahoval naše křestní jméno. V tomto případě použijme ještě jméno bez diakritiky, příště už s diakritikou nebude problém.
4.2.2. Datový model HTML stránky
Náš první servlet sice splnil zadání, jeho nevýhodou je však značná náchylnost k překlepům při sestavování výsledného HTML kódu, chyby pak mohou způsobovat nestandardní chování výsledné stránky. Zápis textu komplikuje také vkládání speciálních HTML znaků do textové části stránky a je vhodné ošetřit také korektní zobrazení znaků s diakritikou. Protože však hledání a následné opravy rutinních chyb nebývá příliš zábavné, poohlédneme se po nějakém řešení, které by nám zmíněné starosti ušetřilo. Jedním z řešení může být sestavení datového modelu popisujícího strukturu HTML stránky, jehož obsah lze převést (automatizovaně) do textového formátu i se všemi formálními náležitostmi jazyka HTML. Dobrá zpráva je, že třídu potřebnou pro sestavení takového modelu nemusíme psát od nuly, ale využijeme volně dostupnou knihovnu z repozitáře Maven s licencí Apache License, version 2.0, vhodnou pro soukromé i komerční použití bez poplatku.
Pro implementaci dalších úkolů použijeme webový modul knihovny Ujorm [23], který poskytuje třídu Element
pro modelování stromu HTML elementů pomocí Java metod (API).
Přínosem této třídy je též jednoduché použití: jakmile totiž získáme kořenový element modelu, pro sestavení zbytku HTML stránky už stačí znát pouze tři metody:
-
setAttribute()
– metoda přiřadí aktuálnímu elementu atribut s hodnotou, -
addElement()
– metoda do objektu přidá nový element s názvem prvního argumentu a jeho instanci vrátí zpět. Další argumenty mohou obsahovat seznam tříd CSS stylů nového elementu, -
addText()
– metoda přidá do těla elementu libovolný text a ošetří zápis speciálních znaků. Pro zápis surového textu (například pro vložení těla Javascriptu) je k dispozici alternativní metodaaddRawText()
.
Třída Element
nabízí ještě přechystané metody pro zápis vybraných HTML elementů, jako je například: nadpis, odstavec, formulář, tabulka a několik dalších.
Za výhodu lze považovat, že chyby při sestavení modelu odhalí už kompilátor, platnost použitých HTML elementů však zůstává nadále na odpovědnosti vývojáře.
Knihovna pomáhá řešit některé problémy s kompatibilitou CSS stylů v prohlížečích, nastavuje správnou kódovou stránku pro zápis textu s diakritikou a omezuje dočasné ukládání stránek v internetovém prohlížeči (cache), nezbytné pro dynamické překreslování stránek.
Podívejme se nyní na zjednodušený diagram třídy Element
.
-
Začněme popisem rozhraní
ApiElement
, jehož metody umožňují sestavit model libovolného XML či HTML souboru.-
Metoda
getText()
poskytuje název stávajícího elementu. -
Metody s prefixem
add
přidávají nový objekt do stávajícího elementu. Pokud se jedná o novýElement
, vrací jeho instanci, jinak metoda vrací referenci na sebe za účelem možného řetězení volaných metod – v takových případech jde zpravidla o přidávání textu. -
Metody s prefixem
set
zapisují do elementu atribut s hodnotou. -
Metoda
close()
z rozhraníCloseable
uzavírá element. K uzavírání elementů dochází automaticky s výjimkou kořenového elementu, který je třeba uzavřít explicitně.
-
-
Třída
Element
nabízí metody poplatné jazyku HTML, konvence prefixuadd
aset
(zavedené v rodičovské třídě) jsou tady ponechány.-
Metoda
setId()
přiřadí k elementu identifikátor, který má být na HTML stránce jedinečný. -
Metoda
setClass()
umí přiřadit jeden či více identifikátorů pro mapování CSS stylů. -
Metoda
setHref()
je užitečná pouze pro element typua
(zkratka je odvozena od anglického slova Anchor). -
Metody
setName()
asetValue()
jsou užitečné při tvorbě HTML formulářů, k tomu se dostaneme později. -
Metoda
addParagraph()
vytvoří novou instanci elementu pro tvorbu odstavce. -
Metoda
addHeading(text)
vytvoří nový nadpis první úrovně.
-
-
Třída
HtmlElement
reprezentuje kořen HTML stromu, poskytuje element hlavičky i těla a obsahuje také konfiguraci, která ovlivňuje způsob zápisu kódu HTML stránky. V neposlední řadě metoda nabízí statické metody pro vytvoření výchozí instance třídyHtmlElement
, která nám usnadní tvorbu HTML hlavičky v ukázkových příkladech.
Je užitečné vědět, že výchozí (defaultní) implementace je obálka nad třídou PrintWriter
, a tak všechny texty zapsané do elementu se posílají rovnou do writeru.
Metoda close()
pak uzavře otevřené elementy a vyprázdní buffer.
Výhodou takového řešení je dobrá performance a minimální paměťová náročnost při sestavování objemných stránek.
Pokud však potřebujeme plnohodnotný paměťový model, lze ho aktivovat změnou konfigurace při sestavení kořenového elementu.
Význam to může mít v případech, kdy se chystáme doplňovat do modelu dodatečně (zpětně) nějaké další elementy. Sestavení původních elementů přitom zůstane beze změny.
Pomocí konfigurace lze změnit také znaky pro odsazování vnořených elementů, zalamovat řádky, změnit národní lokalizaci obsahu, znakovou sadu a několik dalších vlastností.
K dispozici je také podpora uživatelského formátování datových typů, výchozí implementace používá metodu toString()
.
Podívejme se nyní, jak se změní implementace metody doGet()
.
Na první pohled lze pozorovat absenci textových literálů s HTML elementy.
Všimněme si také drobné změny v importech tříd a také parametru anotace @WebServlet
– dostal novou hodnotu pro odlišnou URL adresu servletu.
package net.ponec.jbook.s01_hello;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.ujorm.tools.web.HtmlElement;
import org.ujorm.tools.web.Element;
import net.ponec.jbook.tools.WebTools;
/** A simple servlet implementation */
@WebServlet("/helloByElement")
public class HelloWorldElement extends HttpServlet {
/** Handles the HTTP GET method */
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
HtmlElement html = HtmlElement.niceOf(response, "css/basic.css"); (1)
Element body = html.addBody(); (2)
body.addHeading("Hello, World! (by element)"); (3)
WebTools.addFooter(body, this); (4)
html.close(); (5)
}
}
1 | Statická metoda HtmlElement.niceOf() vytvoří kořenový element HTML stránky a zároveň nastaví v konfiguraci požadavek na odsazování HTML elementů mezerami podobně, jak jsme to prováděli zápisem mezer do zdrojového kódu v první implementaci. |
2 | Metodou addBody() vytvoříme objekt reprezentující HTML element body. |
3 | Do objektu proměnné body zapíšeme požadovaný text nadpisu (heading). |
4 | Do elementu body vložíme ještě standardní patičku stránky, jako tomu bylo u první implementace. |
5 | Metodou close() uzavřeme model a stránku odešleme na internetový prohlížeč. |
Java verze 7 (a vyšší) nabízí alternativní způsob uzavírání objektů, pokud implementují rozhraní Closeable
.
Využívá se k tomu programová konstrukce try
určená pro zachytávání výjimek, a protože právě tohle rozhraní implementuje i třída Element
, dojde při ukončení bloku try
automaticky k uzavření elementu deklarovaného v hlavičce tohoto příkazu.
Tělo metody doGet()
bude po úpravě vypadat takto…
try (HtmlElement html = HtmlElement.niceOf(response, "css/basic.css")) { (1)
html.addBody().addHeading("Hello, World (final)"); (2)
WebTools.addFooter(html, this);
} (3)
1 | Nastavení proměnné html se přesune do kulatých závorek bloku try . |
2 | Nadpis stránky zde zapíšeme technikou řetězení metod objektů.
Protože kořenový element HTML stránky může obsahovat právě jedno tělo, lze pro jeho (nejen opakované) získání využít alternativní metodu getBody() . |
3 | Konec bloku try volá metodu close() automaticky (na objektu deklarovaném v hlavičce bloku). |
Do hlavičky nového (vnořeného) bloku try()
lze vkládat potomky kořenového elementu, a protože taková forma odsazování programového kódu připomíná formátovaný HTML dokument, v dalších příkladech budeme preferovat právě tohle řešení.
Rozšířená implementace
Někdy se může stát, že tvorbu hlavičky potřebujeme mít zcela pod svojí kontrolou, jindy potřebujeme vložit do stránky HTML element, jehož název nemá odpovídající metodu.
Pro oba tyto případy se nám může hodit následující příklad, používající explicitní názvy HTML elementů.
Tahle implementace však předpokládá explicitní konfiguraci objektu response
– včetně výstupní kódové stránky.
Z následující ukázky může být zřejmá výhoda kódu strukturovaného pomocí bloků try()
.
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
try (XmlBuilder html = XmlBuilder.forNiceHtml(response.getWriter())) { (1)
html.setAttrib("lang", "en");
try(XmlBuilder head = html.addElement("head")) { (2)
head.addElement("meta")
.setAttrib("charset", StandardCharsets.UTF_8); (2)
head.addElement("title").addText("Demo"); (3)
head.addElement("link").setAttrib("href", "css/basic.css")
.setAttrib("rel", "stylesheet");
}
try(XmlBuilder body = html.addElement("body")) {
body.addElement("h1").addText("Hello, World! (extended)");
WebTools.addFooter(body, this);
}
}
}
1 | Vytvoření kořenového elementu se podobá sestavení XML dokumentu. |
2 | Nový element HTML vložíme metodou addElement() , kde argument metody obsahuje název. |
3 | Připojíme element title s textem Demo. |
Tato implementace dává prakticky stejnou HTML stránku, v dalších příkladech však vystačíme s tím jednodušším použitím.
Připojení knihovny do projektu
V projektu typu Maven je připojení nové knihovny triviální, stačí zapsat do sekce dependences
souboru pom.xml
následující element, a pak projekt zkompilovat:
<dependency>
<groupId>org.ujorm</groupId>
<artifactId>ujo-tools</artifactId>
<version>2.04</version>
</dependency>
Ukázka napovídá , že soubor bude mít formát XML, závislost na každé knihovně se uvádí uvnitř skupinového elementu dependency
.
Závislost obsahuje tři informace:
-
groupId
– jedinečný identifikátor skupiny projektů v rámci Maven repozitáře, -
artifactId
– jedinečný identifikátor knihovny v rámci skupiny, -
version
– jedinečný identifikátor verze knihovny.
Pohledem do souboru pom.xml
zjistíte, že náš projekt má i další závislosti; jednou z nich je knihovna javax.servlet-api
poskytující popis servletů
(API
) pro kompilaci projektu, jejich skutečnou implementaci však poskytuje až webový kontejner Jetty
za běhu programu.
Závěrem proveďme ve zdrojovém kódu vlastní změny: pod nadpis dopišme nějaký vlastní text, můžeme k tomu použít metodu html.getBody().addText()
, kterou vložíme na nový řádek za nadpis jako samostatný příkaz.
4.2.3. Programové výrazy
V této kapitole využijeme předchozí servlet k prezentaci Java výrazů s jejich výsledky.
Výrazy však nebudeme zapisovat do HTML modelu přímo, ale prostřednictvím pomocného objektu (typu WebPrinter
), který nám bude doplňovat také pořadové číslo výrazu a datový typ výsledku.
Implementace této třídy nebude předmětem našeho zkoumání, zájemci si ji mohou prohlédnout ve staženém projektu.
Tematicky podobné skupiny příkladů jsou zapsané v samostatných metodách s jediným argumentem typu WebPrinter
.
Podívejme se na ukázku volání jedné takové metody; volání ostatních je analogické.
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
try (HtmlElement html = HtmlElement.niceOf(response, "css/table.css")) { (1)
try { (2)
html.getBody().addHeading("Java expressions"); (3)
printPrimitives(WebPrinter.of(html.getBody())); (4)
} catch (Throwable exception) { (5)
html.getBody().addHeading(2,
"A stack trace of exception"); (6)
WebTools.writeStackTrace(html.getBody().addPreformatted(),
exception); (7)
}
WebTools.addFooter(html, this);
}
}
1 | Stránce přiřadíme CSS styl připravený pro tabulkové uspořádání dat. |
2 | Založíme nový blok typu try-catch , který nám umožní zachytávat případné výjimky (vyhozené za běhu programu) za účelem jejich zobrazení v HTML stránce.
Je pravda, že výjimky bychom mohli zachytávat hned v tom prvním bloku try , ale po jejich zachycení už nemáme k dispozici proměnnou html , pomocí které se posílají texty do prohlížeče. |
3 | Zapíšeme nadpis skupiny příkladů. |
4 | Pro zobrazení sledovaných výrazů zavoláme metodu printExamples() s argumentem typu WebPrinter (česky webová tiskárna).
Argument požadovaného typu získáme zavoláním statické metody of() s parametrem typu AbstractElement , což je HTML element, do kterého se budou programové výrazy zapisovat. |
5 | Pokud dojde k vyhození výjimky, zachytí ji blok catch a provedou se příkazy uvnitř tohoto bloku.
V opačném případě bude zpracování pokračovat prvním příkazem, který za tímto blokem následuje.
V našem případě to je tisk standardní patičky stránky. |
6 | Nadpis tematické skupiny bude mít druhou úroveň důležitosti. |
7 | Pro zobrazení výjimky využijeme statickou metodu, jejíž implementací se tady zabývat nebudeme.
Všimněme si však vytvoření nového elementu metodou addPreformatted() , který v textu zachovává mezery a zalomené řádky, což se zpravidla hodí pro různé informace technického charakteru. |
Zkrácená ukázka implementace výrazů nám připomene základní operace s primitivními datovými typy.
První argument metody write()
obsahuje testovaný výraz (nebo proměnnou), druhý argument obsahuje jeho popis.
private void printPrimitives(WebPrinter printer) {
// ...
printer.write(2 + 3L, "2 + 3L"); (1)
printer.write("2" + "3", "\"2\" + \"3\""); (2)
printer.write(2 * 3, "2 * 3"); (3)
printer.write(1 + 2 * 3, "1 + 2 * 3"); (4)
printer.write((1 + 2) * 3, "(1 + 2) * 3"); (5)
printer.write(7 / 3, "7 / 3"); (6)
printer.write(7 % 3, "7 % 3"); (7)
printer.write('A' + (char) 1, "'A' + (char) 1"); (8)
printer.write((char) ('A' + 1), "(char) ('A' + 1)"); (9)
}
1 | Součet dvou primitivních čísel odlišných typů. |
2 | Operátor + aplikovaný na dva objekty typu String vede k jejich spojení.
Všimněme si, jak znaky uvozovek v textových literálech jsou uváděny zpětným lomítkem. |
3 | Ukázka násobení. |
4 | Priorita násobení před sčítáním. |
5 | Priorita vynucená závorkami. |
6 | Výsledek celočíselného dělení se nezaokrouhluje. |
7 | Ukázka výpočtu modulo (zbytku po dělení). |
8 | Výsledkem součtu typu char a int bude int . |
9 | Výraz typu int přetypovaný na char zobrazuje znak. |
Zmíněný kód zobrazí v prohlížeči část tabulky, která začíná pořadovým číslem 16…

První sloupec tabulky zobrazuje pořadové číslo výpisu, druhý výsledek výrazu, třetí sloupec výsledný datový typ a poslední sloupec obsahuje (zpravidla) zdrojový kód výrazu.
Ve zdrojovém kódu můžete narazit na příkaz printer.writeEmptyLine()
, jehož cílem je pouze grafické oddělení řádků.
Všechny ukázky najdete v přiložených příkladech, v případě nejasností kolem metod objektů doporučuji dokumentaci JavaDoc [5].
Řešenou úlohu můžeme doplňovat svými vlastními programovými výrazy podle libosti, prostor pro vlastní experimenty je značný.
4.3. Tvorba tabulek
Tabulkový design má v HTML široké využití, často se používá pro zarovnávání nejrůznějších grafických bloků na stránce, jak jsme si mohli všimnout také v předchozí kapitole.
V následujících kapitolách se zaměříme hlavně na využití HTML elementu table
, je však dobré vědět, že tabulkového vzhledu lze dosáhnout i vhodným použitím CSS stylu v kombinaci s nějakým HTML elementem označujícím blok.
4.3.1. Jednoduchá tabulka
Naším dalším úkolem bude zobrazit v tabulkovém formátu výsledky, které mohou pocházet například z nějakého měření.
Prezentovaná data nám bude poskytovat metoda, která bude vracet (pro začátek) konstanty zapsané ve zdrojovém kódu.
Strukturu tabulky vyznačíme HTML elementem table
, v rámci tohoto elementu se vyznačují řádky elementem tr
(zkratka z anglického výrazu table row) a každý řádek má své buňky, které se vyznačují elementem td
(z anglického výrazu table detail) a jejich hodnoty se zapisují jako text tohoto elementu.
Protože chceme všechny hodnoty v tabulce zarovnat doprava, připojíme do HTML stránky CSS styl, který byl za tímto účelem už vytvořen.
Zápis tabulky o dvou řádcích a třech sloupcích by pak vypadal takto:
<table>
<tr>
<td>@</td>
<td>A</td>
<td>B</td>
</tr>
<tr>
<td>1</td>
<td>493</td>
<td>180</td>
</tr>
</table>
Začněme třeba metodou, která bude poskytovat data tabulky. Protože tabulka je dvourozměrná, hodí se připravit data ve struktuře dvourozměrného pole objektů. Zkrácená ukázka kódu může vypadat takto…
private Object[][] getTableModel() {
Object[][] result = (1)
{ {"@", "A", "B", "C", "D", "E", "F", "G"}
, {"1", 493, 180, 103, 45, 317, 183, 67}
, {"2", 469, 5, 156, 98, 470, 434, 196}
};
return result; (2)
}
1 | Vytvoříme dvourozměrné pole objektů, které uložíme do proměnné result .
Protože třída Objekt je společným rodičem všech tříd, může pojmout objekty typu jak String , tak Integer .
Plnou implementaci najdeme v přiložených příkladech. |
2 | Výsledek vrátíme příkazem return . |
Příkaz for
Pro zpracování výsledku (této metody) budeme potřebovat programovou konstrukci, která umí procházet prvky pole.
V jazyce Java se pro tento účel využívá iterační příkaz for
(česky pro).
Chování tohoto příkazu vysvětlíme na příkladu.
for
Object[] words = {"AA", "BB", "CC"};
for (int x = 0; x < words.length; x = x + 1) { (1)
Object word = words[x]; (2)
}
1 | Nedílnou součástí příkazu for je pár kulatých závorek, který obsahuje tři části oddělené středníkem podle vzoru: (a;b;c) .
|
2 | Tělo bloku for má proměnnou x k dispozici, a tak ji lze využít rovnou jako index pro čtení hodnot pole.
Protože naše pole má právě tři prvky, řídící proměnná x nabývá postupně hodnot od 0 do 2 .
Při každé iteraci se načte hodnota jednoho prvku pole do proměnné word , a protože je typu Object , lze do ní zapisovat prakticky libovolný objekt či pole.
Součástí bloku příkazů může být také vnořený cyklus, který může deklarovat svoji vlastní (novou) řídící proměnnou (s jiným názvem).
Pro předčasné ukončení iterací lze použít break , pro předčasný návrat do další iterace je k dispozici příkaz continue . |
Inkrementace a dekrementace
Zapamatujme si, že inkrementace číselné proměnné primitivního typu provádí zvýšení hodnoty proměnné o jedničku, jedná se o zkrácený zápis příkazu typu x = x + 1
.
Analogicky se používá i dekrementace obsahující dva minusy (x--
), která hodnotu proměnné o jedničku naopak snižuje.
Inkrementace se používá s výhodou v iteračním příkazu for
, kde operátor ++
(či --
) lze zapisovat alternativně před název proměnné: pro nás je teď podstatné, že umístění operátoru nemá vliv na průchod cyklem.
Řešení úkolu
Teď se můžeme vrátit k řešenému příkladu. Náš předchozí servlet (na zobrazování nadpisu) rozšíříme o generování tabulky, a při té příležitosti si ukážeme práci s dvojrozměrným polem objektů. Nebudeme už popisovat celou třídu z minulého řešení, všimneme si hlavně důležitých změn.
protected void doGet(HttpServletRequest request,
HttpServletResponse response) (1)
throws ServletException, IOException {
try (HtmlElement html = HtmlElement.niceOf("The table",
response, "css/table.css")) {
html.getBody().addHeading(html.getTitle());
// Print table: (2)
Object[][] model = getTableModel(); (3)
Element table = html.getBody().addElement("table")
.setClass("numbers"); (4)
for (int y = 0; y < model.length; y++) { (5)
Object[] rowValues = model[y]; (6)
Element rowElement = table.addTableRow(); (7)
for (int x = 0; x < rowValues.length; x++) { (8)
Object value = rowValues[x]; // model[y][x] (9)
rowElement.addTableDetail().addText(value);
} (10)
} (11)
WebTools.addFooter(html, this);
}
}
1 | Deklarace metody doGet() se od minulé kapitoly nezměnila. |
2 | V těle metody je umístěn řádkový komentář, který může usnadnit orientaci v kódu, ale na zpracování nemá vliv. |
3 | Data pro tabulku získáme voláním metody, kterou jsme si popsali před chvílí. |
4 | Na dalším řádku vytvoříme element s názvem table a přiřadíme mu název CSS třídy.
Protože metoda setClass() vrací objekt té stejné tabulky, můžeme přiřazení CSS třídy zřetězit do jediného příkazu. |
5 | Zbývá projít všechny prvky pole a zapsat je do buněk – ve správných elementech tabulky. Pro průchod dvourozměrným polem budeme potřebovat dva cykly, kde první prochází všechny řádky pole a ten druhý prochází všechny prvky vybraného řádku. |
6 | Do proměnné rowValues zapíšeme referenci na dvourozměrné pole obsahující prvky řádku y . |
7 | Metodou addTableRow() vytvoříme novou instanci elementu řádku HTML tabulky a celý postup se opakuje s tím rozdílem, že procházíme jednotlivé prvky nultého řádku tabulky.
Jakmile tělo smyčky dojede na konec, provede se inkrementace proměnné a znovu se vyhodnotí test.
Jakmile jeho podmínka přestane platit, algoritmus pokračuje prvním příkazem za složenou závorkou cyklu. |
8 | Začátek druhého (vnořeného) cyklu for . |
9 | Za zmínku stojí ještě první příkaz vnořeného cyklu, kde získáme hodnotu prvku pole.
Ke stejnému výsledku povede i výraz model[y][x] , vliv na rychlost provedení tady není zásadní. |
10 | Konec druhého (vnořeného) cyklu for . |
11 | Konec prvního cyklu for . |
Věnujme zvýšenou pozornost podmínce ukončení smyčky: chyby tohoto typu zpravidla nevyhazují za běhu programu žádnou výjimku, zpracovávání nekonečné smyčky pak může vést ke ztrátě výkonu celého počítače. |
Po vykreslení naší HTML stránky v internetovém prohlížeči dostaneme na monitoru počítače následující obrázek:

Alternativní procházení pole
Výše uvedený postup procházení pole je zcela korektní, nicméně pokud v těle cyklu nepotřebujeme pracovat s jeho proměnnou (x
, y
), můžeme využít zkrácený zápis.
Smyčka for
pak obsahuje pouze dvě části oddělené dvojtečkou (:
), tělo metody pak může pracovat s proměnnou definovanou v první části.
for (Object[] rowValues : model.length) {
Element rowElement = table.addTableRow();
for (Object value : rowValues) {
// run body ...
}
}
Podmíněný příkaz if-else
Při implementaci algoritmu potřebujeme často provést (jeden či více) příkazů pouze při splnění nějaké podmínky.
Pro tento účel se používá podmíněný příkaz if
s kulatou závorkou obsahující logický výraz.
Pokud je výraz vyhodnocen jako pravdivý, provede se blok příkazů uvedený za příkazem if
, který se zapisuje do složených závorek.
Složené závorky lze vynechat v případě, že blok obsahuje právě jeden příkaz, tohle řešení se však kvůli horší přehlednosti příliš nedporučuje.
Za blokem příkazů může (ale nemusí) následovat klíčové slovo else
, které uvádí další blok příkazů pro alternativní zpracování.
Výrazy if
a else
lze s výhodou řetězit.
Podívejme se na jednoduchou ukázku, praktické využití předvedeme v další kapitole.
if (y == 0) { (1)
statementA();
} else if (x == 0) { (2)
statementB();
} else { (3)
statementC();
}
1 | Podmíněný příkaz s podmínkou. Pokud je podmínka splněna, zavolá se metoda statementA() . |
2 | Alternativní podmínka se vyhodnotí v případě, že předcházející podmínka nevyhověla. Tuhle formu zápisu lze po skončení bloku opakovat pro podmínky další. |
3 | Do posledního bloku spadnou všechny případy, které nezachytí žádná z předchozích podmínek. |
Tabulka náhodných čísel
Na posledním servletu jsme si ukázali procházení prvků vícerozměrného pole.
V dalších příkladech však budeme využívat spíše metodu třídy Element
zvanou addTable()
, která implementuje popsaný algoritmus tak, že jí stačí předat pouze referenci na dvourozměrné pole, a metoda sestaví HTML model tabulky sama.
html.getBody().addTable(getTableModel(8), "numbers");
Nyní můžeme tu původní metodu (pro poskytování dvourozměrného pole) upravit tak, aby vracela (dvojrozměrné) pole náhodně generovaných čísel v intervalu od 0
do 500
, přitom první řádek a první sloupec budou obsahovat popis sloupce podle vzoru tabulkového editoru Excel
.
Počet řádků bude shodný s počtem sloupců tabulky a toto číslo dostane metoda prostřednictvím svého argumentu.
Podívejme se na zdrojový kód přepracované metody následovaný popisem.
/** Create a random number array (1)
* @param size Number of cells per a square table side (including labels)
* @return A result
*/
private Object[][] getTableModel(int size) { (2)
Object[][] result = new Object[size][size]; (3)
Random random = new Random(); (4)
for (int y = 0; y < size; y++) { (5)
for (int x = 0; x < size; x++) {
if (y == 0) { (6)
result[y][x] = (char) ('A' + x - 1); (7)
} else if (x == 0) { (8)
result[y][x] = y;
} else {
result[y][x] = random.nextInt(500); (9)
}
}
}
}
1 | Metodě předchází její popis ve formátu JavaDoc, pomocí značek @param a @return lze popisovat vstupní parametr a návratovou hodnotu. |
2 | Metoda getTableModel() očekává jeden parametr s názvem size primitivního typu int a vrací dvourozměrné pole objektů. |
3 | Na prvním řádku vytvoříme dvourozměrné pole velikosti size (z parametru metody), kde každý prvek má hodnotu null .
Pole bude třeba naplnit hodnotami. |
4 | Vytvoříme objekt typu Random , který obsahuje metody vhodné pro generování náhodných čísel. |
5 | Projdeme všechny prvky dvojrozměrného pole tak, abychom do něj mohli zapsat hodnoty. |
6 | Uvnitř vnořeného cyklu máme k dispozici obě souřadnice x a y .
Do prvního sloupce chceme zapisovat popis řádku, a na to bude potřeba použít podmíněné příkazy.
Logická podmínka se zapisuje do kulaté závorky za klíčové slovo if .
Pokud je podmínka pravdivá, provedou se všechny příkazy uvnitř složené závorky.
Za složenou závorkou podmínky if může následovat klíčové slovo else se složenou závorkou obsahující příkazy, které se naopak provádějí, když je podmínka neplatná.
Připomeňme, že příkazy else a if lze s výhodou řetězit.
Forma zápisu do buňky se podobá jeho čtení. |
7 | Při zápisu prvního řádku zapíšeme postupně znaky tabulkové sady Unicode od znaku A , protože však první sloupec data neobsahuje, přiřadíme mu znak, který písmenu A předchází.
Pokud sečteme čísla (rozdílných) typů char a int , výsledek bude mít typ s větším rozsahem.
Protože do buňky však chceme zapsat objekt typu Character , je třeba číslo nejdříve přetypovat pomocí cílového datového typu v závorce (char) .
Teprve potom může Java převést primitivní typ na objekt typu Character – díky vlastnosti autoboxing .
Zápis do buňky se provede v případech, kde proměnná y se rovná nule . |
8 | Jinak otestujeme, zdali se nejedná o zápis do prvního sloupce, který poznáme porovnáním proměnné x s hodnotu nula .
V tom případě do buňky zapíšeme pořadové číslo řádku. |
9 | Jinak do buňky zapíšeme náhodné číslo podle zadání, k tomu využijeme metodu nextInt() objektu typu Random . |
Výsledek si můžeme ověřit na spouštěném projektu, kde si můžeme všimnout, že při každém novém otevření stránky se zobrazují jiná čísla.
Závěrem proveďme ve zdrojovém kódu vlastní změny: nahraďme náhodná čísla tabulky náhodně generovanými slovy.
Pro začátek k tomu můžeme využít pole (s prvky typu String
) obsahující předem připravený seznam slov, později můžeme zkusit generovat i obsah těchto slov.
4.3.2. Přehled vozidel
V kapitole Dědičnost tříd jsme si popsali diagram tříd na příkladu evidence vozidel.
Naším úkolem je zavolat službu, která nám poskytne několik záznamů (blíže neurčený počet) o vozidlech, a část těchto údajů vypíšeme do HTML tabulky.
Třídy popisující datový model (Car
, Company
, RoadVehicle
) se někdy nazývají doménové objekty.
Při návrhu tříd bývá dobrým zvykem datovou část oddělit do doménových objektů a algoritmy umístit raději do tříd servisního typu.
Implementace setterů a getterů v doménových objektech je spíše rutinní práce, kterou zvládne vygenerovat (na vyžádání) většina specializovaných editorů IDE podle atributů třídy – včetně JavaDoc.
Ukázky reálných doménových objektů lze najít v přiložených příkladech.
Zajímavější bude zřejmě sestavení datového modelu tabulky, který implementujeme do metody getTableModel()
, popis následuje…
/**
* A dynamic number array
* @param size Number of cells per a square table side (including labels)
* @return A result
*/
private List<Object[]> getTableModel() { (1)
List<Object[]> result = new ArrayList<>();
Object[] header = { "Order", "Manufacturer", "Model",
"Made", "Serial number", "Power [kW]",
"Energy", "Trunk [l]", "Owner"};
result.add(header);
for (Car car : vehicleService.getFictionalCars()) {
String ownerName = car.getOwner().getFirstname() (2)
+ " "
+ car.getOwner().getSurname();
Object[] row = { result.size() (3)
, car.getModel().getManufacturer().getName()
, car.getModel().getName()
, car.getMade()
, car.getMotorSerialNumber()
, car.getEnginePower()
, car.getEnergy().getName()
, car.getTrunkVolume()
, ownerName
};
result.add(row);
}
return result;
}
1 | První změnou je nový typ návratové hodnoty List<Object[]> .
Jak už jsme zmínili v kapitole List, jedná se o rozhraní (interface), které se používá jako dynamické pole objektů .
Typ prvků se deklaruje pomocí generického typu, který se uvádí ve špičatých závorkách, my zde budeme potřebovat seznam jednorozměrných polí.
Na prvním řádku metody jsme získali prázdný návratový objekt, který zbývá naplnit daty. |
2 | Připomeňme si spojování objektů typu String operátorem + .
V kapitole Třída String jsou popsány některé další způsoby spojování řetězců. |
3 | Pro každý prvek seznamu vytvoříme pole objektů, které vložíme do objektu typu List metodou add() .
Počet skutečně zapsaných prvků listu lze zjistit metodou size() .
Na rozdíl od pole přistupujeme k objektu typu List výhradně pomocí jeho metod. |
Výsledná HTML stránka vykreslená v internetovém prohlížeči bude vypadat asi takto:

Závěrem proveďme ve zdrojovém kódu vlastní změny: změňme pořadí některých sloupců tabulky, dále můžeme výkon motoru přepočítávat na jednotky v koních [HP], což je zastaralá jednotka výkonu.
4.4. Formuláře
Dosud jsme tvořili jen HTML stránky prezentující serverová data, nyní se zaměříme také na datové vstupy z klávesnice.
4.4.1. Formulář s jedním vstupním elementem
Začněme jednoduchým úkolem: nabídneme uživateli formulář s jednou položkou pro zápis textu, který se bude na server odesílat tlačítkem.
Server si textovou zprávu vyzvedne a vloží ji zpět do nové stránky.
Následuje ukázka zápisu jednoduchého formuláře v jazyce HTML, která se bude nacházet někde uvnitř HTML elementu body
…
<form method="post" action="?">
<label>Some text</label>
<input type="text" name="text" value="Enter some text"/>
<input type="submit" value="Submit"/>
</form>
Element input
typu submit
zobrazí tlačítko, které po kliknutí myší odešle na server všechny hodnoty vstupních elementů, jež se nacházejí uvnitř formuláře form
.
HTTP metoda, kterou se data odesílají na server, je určena hodnotou atributu method
.
My budeme používat pro odesílání formuláře dvě metody:
-
get
– data se odesílají jako parametry URL adresy a jejich hodnoty vidí uživatel v adresním řádku internetového prohlížeče; parametry tak mohou být ukládány do záložek či historie prohlížení. Metoda je vhodná spíše pro kratší zprávy veřejné povahy. Takovou zprávu servlet zpracuje metodoudoGet()
, -
post
– data jsou obsažena v těle HTTP dotazu, a tak je uživatel v adresním řádku prohlížeče nevidí. Tahle metoda je vhodná pro posílání důvěrných informací (například hesla), přitom zvládá dobře i dlouhé zprávy včetně diakritiky. Tuhle zprávu servlet zpracuje metodoudoPost()
.
Náš HTML formulář obsahuje ještě druhý atribut s názvem action
, který popisuje URL té stránky (v našem případě URL servletu), na niž má internetový prohlížeč uživatelské vstupy odeslat.
Pokud je cílem týž URL, z něhož byla stránka načtena, pak jeho hodnotou může být otazník, který se používá pro oddělení URL parametrů.
Podívejme se na další elementy uvnitř formuláře:
-
Element
label
obsahuje jmenovku vstupního pole. -
Druhý je element
input
typutext
, do kterého uživatel bude zapisovat zprávu. Atributname
obsahuje název parametru, který použijeme pro získání zprávy na servletu, atributvalue
může obsahovat předvyplněný text vstupního pole. -
Poslední element formuláře je
input
typusubmit
, který uživateli zobrazí tlačítko pro odeslání formuláře na server.
Podívejme se, jak bude vypadat řešení v jazyce Java, pro zjednodušení jsem vynechal v servletu definici balíčku, importy a JavaDoc. Úplnou verzi třídy najdeme v ukázkovém příkladě s následným popisem.
@WebServlet( "/simpleFormServlet")
public class SimpleFormServlet extends HttpServlet {
private static final Charset CHARSET = StandardCharsets.UTF_8; (1)
private static final String PARAMETER_NAME = "text";
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding(CHARSET.toString());
try (HtmlElement html = HtmlElement.niceOf("Simple form",
response, CHARSET, "css/form.css")) { (2)
html.getBody().addHeading(html.getTitle());
Element form = html.getBody().addForm()
.setMethod(Html.V_POST)
.setAction(request.getRequestURI());
try (Element line = form.addDiv()) { (3)
line.addLabel().addText("Some text");
line.addTextInput()
.setName(PARAMETER_NAME)
.setValue(request.getParameter(PARAMETER_NAME)); (4)
}
form.addInput()
.setType(Html.V_SUBMIT)
.setValue("Submit");
WebTools.addFooter(html, this);
}
}
@Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) (5)
throws ServletException, IOException {
doGet(request, response);
}
}
1 | Třída obsahuje dva statické atributy, které bývají označovány klíčovým slovem final pro garanci neměnnosti hodnoty za běhu programu.
Takovým atributům se říká konstanty.
Podle konvence se statické atributy třídy zapisují velkými písmeny.
|
2 | Následuje sestavení kořenového elementu, všimněte si metody niceOf() , která dostane prostřednictvím argumentu referenci na objekt kódové stránky pro správné zobrazení diakritiky v HTML stránce. |
3 | Na rozdíl od ukázky HTML kódu elementy label a input vložíme do neviditelného rámečku vytvořeného elementem div pro lepší formátování výsledné HTML stránky. |
4 | Důležitá metoda pro získání hodnoty parametru z objektu request se jmenuje getParameter() .
Získanou hodnotu hned odešleme do elementu metodou setValue() . |
5 | Protože formulář odesíláme metodou post , zprávu je třeba zachytit v servletu metodou doPost() .
Protože se však zpracování neliší od zpráv zasílaných metodou get , v metodě jednoduše zavoláme metodu doGet() se stejnými parametry.
Díky takovému řešení bude HTML stránka reagovat na parametry zaslané v URL (skoro) stejně jako na parametry odeslané z grafického formuláře metodou post . |
Výsledná obrazovka v internetovém prohlížeči může vypadat takto:

Tento jednoduchý formulář brzy doplníme o další položky.
4.4.2. Formulář s více vstupními elementy
Reálné formuláře bývají obsahově bohatší a mohou obsahovat i komponenty vhodné pro vkládání logických hodnot (ano/ne) nebo pro výběr hodnoty ze seznamu (combo-box). Podrobnější popis výčtu dalších HTML elementů poskytuje třeba zdroj [20]. Nyní v našem formuláři zobrazíme editační položky popsané následující tabulkou.
Jmenovka | Anglicky | Podmínka | Typ |
---|---|---|---|
Jméno |
First name |
2–30 libovolných znaků |
text |
Příjmení |
Last name |
2–30 libovolných znaků |
text |
tři skupiny malých abecedních znaků bez diakritiky oddělené zavináčem |
text |
||
Telefon |
Phone number |
9–15 číslic, tolerovány jsou mezery a znaky plus ( |
text |
Přezdívka |
Nickname |
3–10 znaků, tolerovány jsou mezery a znaky plus ( |
text |
Odeslat |
Submit |
(tlačítko pro odeslání tady nepotřebuje podmínku) |
submit |
Implementace servletu vychází z předchozího formuláře, případné zprávy o neplatných datech se budou zobrazovat pod každým vstupním políčkem dynamicky. Platnost vstupů budeme vyhodnocovat pomocí takzvaných regulárních výrazů, podívejme se na ně.
Regulární výrazy
Regulární výraz je textový řetězec sestavený podle určitých pravidel k porovnávání (či prohledávání) textových dat pomocí zástupných výrazů. Fungování budeme demonstrovat na anglickém citátu britské spisovatelky:
As is a tale, so is life: not how long it is, but how good it is, is what matters.
Česky to znamená: Život je jako příběh: nezáleží na tom, jak je dlouhý, ale jak je dobrý.
Mějme tento anglický citát v objektu typu String
a zkusme v něm vyhledat text o čtyřech znacích, který začíná písmenem l
a končí e
.
Pro popis hledaného výrazu by se nám hodil znak připomínající žolíka, který v některých karetních hrách nahrazuje kartu konkrétní hodnoty.
Regulární výraz nabízí něco podobného, je však třeba znát pravidla jeho použití.
Znaky bez speciálního významu jsou malá či velká čísla abecedy a číslice.
Nejdůležitější znaky speciálního významu jsou popsány tady:
-
.
(tečka) je výraz reprezentující jeden libovolný znak. Regulární výraz pro hledání výše popsaného požadavku je"l..e"
. Pro vyhledání stačí zavolat vhodnou metodu s parametry bez nutnosti složitého programování. -
[]
(hranaté závorky) ohraničují seznam povolených znaků. Výraz[Ll]ife
reprezentuje slovo bez ohledu na velikost prvního písmene. Znak pomlčky vymezuje interval, a tak výraz[A-Z]
reprezentuje jeden znak velké abecedy (bez diakritiky) a výraz[0-9]
reprezentuje číslici. -
\
(zpětné lomítko) ruší speciální význam znaků a naopak – z některých běžných znaků dělá speciální. Pokud tedy chceme v textu popsat tečku, je třeba sestavit regulární výraz"\."
.Připomeňme si, že zpětné lomítko je speciální znak také v jazyce Java, a proto ve zdrojovém kódu je nutné ho zdvojit dle vzoru: "\\."
. -
*
(hvězdička) označuje nula a více opakování předcházejícího znaku (případně výrazu). Regulární výraz"l.*e"
má shodu se všemi slovy: life, lxe, le. -
+
(plus) značí jedno a více opakování předcházejícího znaku (případně výrazu). -
{}
(složené závorky) s číslem vyjadřují počet opakování předcházejícího znaku (či výrazu). Vložením pomlčky lze vyjádřit také interval podle vzoru:{1-3}
. -
()
(kulaté závorky) slouží k vyznačování skupin, oddělovačem je znak|
. Například výraz(tale|life)
reprezentuje jedno ze slov tale či life. Kulaté závorky mají své uplatnění také při nahrazování textů. -
^
a$
– první znak symbolizuje počátek textu, ten druhý jeho konec. Uvedením obou symbolů ve výrazu lze kontrolovat obsah celého textu proti požadovanému výrazu. Uvedený citát má shodu s regulárním výrazem"^As.*rs\.$"
.
Speciálních znaků je celá řada, znaky je možné v seznamech i vylučovat.
Dobrá zpráva je, že regulární výrazy se dají využít v celé řadě oblastí mimo jazyk Java, na druhou stranu i regulární výrazy mají své meze a na některé specifické potřeby už nestačí.
Podrobnější popis lze najít také v JavaDoc [5] u třídy RegExp
.
Formulář osobních údajů
Vraťme se zpět k implementaci našeho formuláře.
Na rozdíl od minulého řešení si pro popis editačního pole připravíme objektový model, jeho třídu pojmenujme třeba FieldDescriptor
.
Tento model bude mít čtyři atributy, jejichž hodnoty budou neměnné po celou dobu životnosti objektu.
Takovou vlastnost lze garantovat klíčovým slovem final
, které se zapisuje před datovým typem atributu.
Hodnoty pak budeme vkládat do objektu pomocí konstruktoru.
Podívejme se na ukázku.
public class FieldDescriptor {
private final String label; (1)
private final String name; (2)
private final String regexp; (3)
private final boolean submit; (4)
public FieldDescriptor(String label, String name,
String regexp, boolean submit) { (5)
this.label = label; (6)
this.name = name;
this.regexp = regexp;
this.submit = submit;
}
public FieldDescriptor(String label, String key, String regexp) { (7)
this(label, key, regexp, false);(8)
}
public String getValue(HttpServletRequest request) { (9)
if (submit) {
return "Submit"; (10)
} else {
return request.getParameter(name); (11)
}
}
// Write getters .... (12)
1 | Jmenovka, která se bude zobrazovat u editovaného pole.
Díky klíčovému slovu final bude pokus o jiný zápis do atributu odhalen už při kompilaci zdrojového kódu. |
2 | Název input elementu na HTML stránce. |
3 | Regulární výraz pro kontrolu platnosti. |
4 | Tento atribut obsahuje žádost o sestavení odesílacího tlačítka. |
5 | Seznam parametrů konstruktoru se odděluje čárkou, podobně, jako je tomu u parametrů metod. |
6 | Pokud se název argumentu shoduje s atributem, pak je třeba atribut odlišit předřazením klíčového slova this odděleným tečkou. |
7 | Třída může mít několik konstruktorů, pokud se liší počtem parametrů či typem. Této konstrukci se říká přetížený konstruktor. |
8 | V těle konstruktoru doplníme hodnotu argumentu submit a pomocí klíčového slova this delegujeme vytvoření objektu na konstruktor jiný.
Pomocí klíčového slova super() lze volat také konstruktor rodičovské třídy. |
9 | Tahle metoda nám bude poskytovat hodnotu parametru z objektu HttpServletRequest . |
10 | V případě tlačítka budeme vracet konstantu. |
11 | Pro ostatní položky vrátíme výsledek metody getParameter() , kde argumentem bude jméno HTML elementu z editačního formuláře. |
12 | Atributy zpřístupníme sadou getterů (settery zde nemají význam). |
Teď už máme připravenou třídu pro modelování položek formuláře a připravíme si metodu, která objektový model sestaví.
Podívejme se na implementaci metody getFieldDescriptors()
…
private FieldDescriptor[] getFieldDescriptors() {
FieldDescriptor[] result = (1)
{ new FieldDescriptor("First name", "firstname", ".{2,30}") (2)
, new FieldDescriptor("Last name", "lastname", ".{2,30}")
, new FieldDescriptor("E-mail", "email",
"[a-z0-9.-]+@[a-z0-9.-]+\\.[a-z]{2,4}") (3)
, new FieldDescriptor("Phone number", "phone", "[+ 0-9]{9,15}")
, new FieldDescriptor("Nickname", "nick", ".{3,10}")
, new FieldDescriptor(" ", "submit", "", true)}; (4)
return result;
}
1 | Do proměnné result zapíšeme pole objektů typu FieldDescriptor . |
2 | Vytvoříme objekt pomocí konstruktoru se třemi parametry, kde atribut submit dostává hodnotu false .
Posledním argumentem konstruktoru je regulární výraz, který očekává 2 až 30 libovolných znaků.
Protože regulární výrazy budeme hodnotit metodou matches() , symboly počátku a konce textu jsou zde nadbytečné. |
3 | Stručný popis regulárního výrazu: očekává se alespoň jeden znak z první skupiny (v hranatých závorkách), dále znak zavináč (@ ), alespoň jeden znak z druhé skupiny, tečka (. ) a dva až čtyři znaky z poslední skupiny. |
4 | Na tomto řádku využijeme konstruktor se čtyřmi parametry. |
Popišme si metodu doGet()
, která sestaví výsledný HTML model, a připomeňme, že metoda doPost()
deleguje své zpracování právě sem…
@Override
protected void doGet(HttpServletRequest request,
HttpServletResponse response)
throws ServletException, IOException {
try (HtmlElement html = HtmlElement.niceOf("Simple user form",
response, CHARSET, "css/form.css")) {
html.getBody().addHeading(html.getTitle());
Element form = html.getBody().addForm()
.setMethod(Html.V_POST)
.setAction(request.getRequestURI()); (1)
for (FieldDescriptor field : getFieldDescriptors()) { (2)
createInputField(field, form, request, postMethod);(3)
}
WebTools.addFooter(html, this);
}
}
1 | Atribut formulářového elementu action bude obsahovat hodnotu request.getRequestURI() , což je alternativní řešení pro odeslání parametrů (formuláře) pro ten stejný servlet.
Atribut umožňuje odeslat parametry formuláře na jiný servlet. |
2 | V cyklu projdeme postupně každý model z pole, který nám poskytne metoda getFieldDescriptors() . |
3 | Potřebný HTML model sestavíme v metodě createInputField() se čtyřmi argumenty, tuhle metodu si popíšeme zvlášť. |
private void createInputField(FieldDescriptor field,
Element form, HttpServletRequest request, boolean postMethod) {
String cssStyle = field.isSubmit() ? "submit" : null; (1)
Element result = form.addDiv(cssStyle); (2)
result.addLabel().setFor(field.getName())
.addText(field.getLabel());
Element inputRow = result.addDiv();
inputRow.addInput()
.setType(field.isSubmit() ? Html.V_SUBMIT : Html.V_TEXT) (3)
.setName(field.getName())
.setValue(field.getValue(request));
String errorMessage = getErrorMessage(request, field); (4)
if (errorMessage != null) {
inputRow.addSpan().addText(errorMessage);
}
}
1 | Výraz s otazníkem uvedený v argumentu metody se jmenuje ternární operátor a jeho princip si vysvětlíme za chvíli. |
2 | Příkaz vytvoří v HTML stránce neviditelný rámeček, který bude obsahovat jmenovku se vstupním polem a případně chybovou zprávu.
Hodnota přiřazeného CSS stylu závisí na typu vstupního pole. |
3 | Vstupní komponentě přiřadíme postupně: typ, jméno a hodnotu. |
4 | Vyhodnotíme platnost hodnoty pole z HTTP parametru.
Pokud je argument neplatný, vrátí chybovou zprávu pro zobrazení uživateli, jinak vrátí null .
Obsahem chybové zprávy se řídí její zápis do formuláře. |
Ternární operátor
Příkaz typu if-else
umožňuje vytvářet bloky příkazů, které se vykonají při splnění nějaké podmínky.
Někdy se nám však hodí stručnější forma podmíněného výrazu, který umí vrátit jedinou hodnotu.
Takovému výrazu se říká ternární operátor a skládá se ze tří částí…
field.isSubmit() ? Html.V_SUBMIT : Html.V_TEXT
Výraz před otazníkem představuje řídící podmínku.
Za otazníkem jsou pak ještě dva výrazy stejného typu oddělené dvojtečkou (:
).
Pokud je řídící podmínka pravdivá, použije se první z nich, jinak ten druhý výraz.
Ternární operátor využijeme ještě při zápisu typu input
elementu.
Pokud vytváříme tlačítko, použijeme hodnotu "submit"
(která je obsažena v konstantě Html.V_SUBMIT
), v opačném případě zapíšeme hodnotu "text"
(který je obsažen ve statické konstantě Html.V_TEXT
.
V poslední části metody createInputField()
voláme metodu na vyhodnocení vstupního parametru.
Pokud se chybová zpráva liší od hodnoty null
, pak tuto zprávu zapíšeme do stromu elementů pomocí HTML značky span
.
Dále se podíváme, jak se regulární výrazy vyhodnocují.
Hodnocení platnosti textového parametru
Zbývá popsat metodu pro vyhodnocení platnosti parametrů pomocí regulárního výrazu.
public String getErrorMessage(HttpServletRequest request,
FieldDescriptor field) {
boolean postMethod = "post".equalsIgnoreCase(request.getMethod()); (1)
if (postMethod && !field.isSubmit()) { (2)
final String value = request.getParameter(field.getName()); (3)
if (value == null || value.isEmpty()) { (4)
return "Required field";
} else if (!Pattern.matches(field.getRegexp(), value)) { (5)
// Localize it:
return "Wrong value for: ".concat(field.getRegexp());
}
}
return null;
}
1 | Zjistíme typ HTTP metody, kterou byl požadavek z prohlížeče odeslán. |
2 | Platnost hodnoty chceme kontrolovat jen u požadavku zaslaného typem post , kontrolovat hodnotu potvrzovacího tlačítka nemá smysl. |
3 | Do proměnné value zapíšeme hodnotu parametru. |
4 | Jinak se obsah textu porovná s regulárním výrazem metodou matches() (česky odpovídá).
Pokud hodnota neodpovídá očekávání, vrátíme zprávu obsahující (pro jednoduchost) celý regulární výraz. |
Tak, máme hotovo a můžeme si prohlédnout výsledek po vložení testovacích dat…

Na internetu lze najít tipy na sestavení nejrůznějších regulárních výrazů – nejen pro kontrolu e‑mailu. Chybové zprávy reálných aplikací by měly být správně uživatelsky přívětivé.
Závěrem proveďme ve zdrojovém kódu vlastní změny: doplňme do formuláře nový vstup pro osobní číslo uživatele, povoleny budou jen číselné znaky.
Dále zkusme do formuláře zapracovat souhlas se zpracováním osobních údajů pomocí komponenty typu checkbox
.
S pomocí dokumentace k regulárním výrazům [27] zkusme vyznačit (třeba hranatými závorkami) všechna slova z uvedeného citátu, která obsahují písmeno l
.
Řešení lze najít ve třídě RegexpTest
v přiloženém projektu.
4.4.3. Počítání slov
V dalším příkladu si ukážeme počítání znaků a slov v textu pro nějakou statistiku.
Za slovo můžeme považovat každou skupinu znaků oddělenou mezerou, novým řádkem či vzájemnou kombinací takových znaků v libovolném počtu.
Znakům pro oddělování slov se někdy říká bílé znaky a do této skupiny patří také různé mezery a tabulátory.
Na výsledné HTML stránce bude možné pozorovat, jak zásadní vliv může mít nastavení CSS třídy na umístění jmenovky.
Původní element pro vkládání textu nahradíme novým elementem textarea
, který umožňuje zápis více řádků ve formuláři.
Výsledek zobrazíme v tabulkovém formátu.
Datový model nám poskytne metoda, která obdrží (ve svém parametru) požadovaný text.
private Object[][] getGridModel(String message) {
if (message == null) {
message = ""; (1)
}
final Object[][] result =
{ {" ", "Count", "Unit"}
, {"Word count:", countWords(message), "words" } (2)
, {"Non-white charactes:",
countNonWhiteCharacters(message), "characters"}
, {"Message length:", message.length(), "characters"} (3)
};
return result;
}
1 | Pokud zpráva obsahuje hodnotu null , nahradíme její obsah prázdným textem, abychom zabránili výjimce při dalším zpracování.
Na tomto řešení lze pozorovat, že k parametrům metody můžeme přistupovat podobně jako k běžným proměnným. |
2 | Sestavit dvourozměrné pole už umíme, hodnotu prvku lze získat samozřejmě libovolným výrazem – včetně volání metody. |
3 | Připomeňme, že počet znaků objektu String poskytuje metoda message.length() . |
Podívejme se na implementaci statistických výpočtů…
private int countWords(String message) {
int result = 0; (1)
boolean lastWhitespace = true; (2)
for (int i = 0; i < message.length(); i++) { (3)
boolean currentWhitespace
= Character.isWhitespace(message.charAt(i)); (4)
if (lastWhitespace != currentWhitespace) { (5)
lastWhitespace = currentWhitespace;
if (!currentWhitespace) { (6)
++result;
}
}
}
return result;
}
1 | Počet nalezených slov budeme zapisovat do proměnné result . |
2 | V textu budeme sledovat dva typy znaků – pro slova a pro mezery. Připravíme si proměnnou pro informaci o tom, jakého typu byl poslední zpracovaný znak. |
3 | V cyklu projdeme postupně každý znak parametru message , řídící proměnná i bude obsahovat index aktuálního znaku. |
4 | Znak parametru (na dané pozici) získáme metodou charAt(i) a pro kontrolu bílého znaku využijeme statickou metodu Character.isWhitespace() ze standardní knihovny Java. |
5 | Pokud dojde ke změně typu znaku, změníme obsah logické proměnné lastWhitespace . |
6 | Pokud předcházející znak nebyl bílým znakem, přičteme k výsledku jedničku.
Připomeňme si, že negace logického typu se provádí operátorem ! (vykřičník), čímž se hodnota false změní na true a naopak. |
Implementace metody countNonWhiteCharacters()
stojí na podobných principech.
HTML stránka by mohla po spuštění vypadat takto:

Řešení celého servletu najdeme v ukázkových příkladech.
Závěrem proveďme ve zdrojovém kódu vlastní změny: Doplňme tabulku výsledků o počet velkých písmen, variantou je počítat pouze číslovky.
Metodu vhodnou pro rozpoznávání potřebných znaků najdeme v dokumentaci JavaDoc ke třídě Character
.
4.5. Zábava
V této sekci najdeme dvě interaktivní aplikace a jednoduchou strategickou hru.
4.5.1. Bodové kreslení
V následujícím řešení si vyzkoušíme sestavení dvoubarevného obrazu, kde jednotlivé body budeme zakreslovat do mřížky kliknutím myši, výsledný vzhled by se měl blížit následujícímu obrázku.

Mřížku vykreslíme pomocí HTML elementu table
, kde do každé buňky umístíme jedno tlačítko button
typu submit
(bez popisu) a do atributu name
zapíšeme jednoznačný identifikátor tlačítka, celá tabulka musí být uvnitř elementu form
.
Pro práci s mřížkou budeme potřebovat datový model, který postavíme na třídě SimpleBoardModel
.
Podívejme se na diagram tříd servletu s modelem:
Třída SimpleBoardModel
obsahuje šířku a výšku kreslicí desky (v počtu buněk) a nějaký objekt na popis stavu jednotlivých bodů mřížky.
Zde se také nabízí použít dvourozměrné pole primitivních logických hodnot boolean[][]
, my však půjdeme jinou cestou a využijeme objekt typu BitSet
.
Jeho výhodou je snadná manipulace s daty a menší paměťové nároky, nevýhodou je jeho jednorozměrnost, kterou však lze napravit v aplikačním rozhraní (API) modelu mřížky.
Každému bodu přiřadíme identifikační číslo, začneme vlevo nahoře hodnotou 0
a pokračujeme směrem doprava a na konci řádku přejdeme na řádek nižší.
Tímto způsobem očíslujeme celou mřížku.
Abychom však práci s body modelu typu SimpleBoardModel
zpřehlednili, nabídneme metody s parametry obou souřadnic, které v implementaci přepočítáme na identifikátory buněk.
Využijeme pro to operátor %
(modulo).
Popišme si jednotlivé metody modelu:
-
flipStone(x, y)
– metoda změní logickou hodnotu buňky v mřížce, kterou si můžeme představit jako kamínek tmavé barvy umístěný do mřížky: prvním zavoláním metody kamínek do mřížky vložíme, druhým zavoláním kamínek zase odstraníme. Buňka v tabulce je určena souřadnicemix
ay
, -
flipStone(i)
– metoda stejného jména také změní logickou hodnotu buňky, její pozice je však určena pořadovým číslem buňky v jednorozměrném číslování, které se podobá indexu jednorozměrného pole. Připomeňme si, že jedna třída může obsahovat více metod stejného jména, pokud se liší počtem nebo typem parametrů. Takovému zápisu říkáme přetěžování metod, -
hasStone(x, y)
– metoda kontroluje přítomnost kamínku na dané souřadnici, -
metody
getWidth()
agetHeight()
nám poskytují výšku a šířku mřížky, -
exportBoard()
– metoda převede stav modelu do objektuString
. Pokud totiž uživatel klikne myší na nějakou buňku, musí formulář na server poslat (kromě jejího identifikátoru) také poslední stav hrací desky, pomocí kterého server provede rekonstrukci objektového modelu. Za tímto účelem doplníme do HTML stránky ještě neviditelný input element typuhidden
, který bude ten původní stav hrací desky poskytovat.
Zpracování requestu v servletu BoardServlet
bude následující:
-
Servlet při zpracování HTTP požadavku zkusí najít parametr dotazu se jménem
board
. -
Pokud ho nenajde, založí logicky prázdný model, jinak se pokusí obnovit stav modelu pomocí textové hodnoty tohoto parametru. V případě zachycení výjimky založí logicky prázdný model.
-
Třída
BoardServlet
pak získá identifikátor tlačítka, které odeslalo formulář na server, a tento identifikátor převede na pořadové číslo bodu v datovém modelu mřížky. -
Provede se změna stavu bodu zavoláním metody
flipStone(i)
. -
Podle modelu mřížky sestavíme datový model HTML elementů, buňky obsahující body (kamínky) přitom označíme CSS třídou s hodnotou
"s"
(stone), vykreslení kolečka v tabulce však přenechme pro začátek spíše webovým grafikům. -
Model HTML se odešle na webový prohlížeč nejpozději při uzavření kořenového elementu. A to je v principu vše.
Řešení najdeme v přiložených příkladech, my se zaměříme spíše na nové programové výrazy a konstrukce.
Pomocná třída Base64Converter
Data získaná z objektu BitSet
je třeba převést na 64znakovou sadu označovanou jako Base64, jejíž znaky lze bezpečně posílat po internetu.
Protože délka takového textu je zbytečně velká, příslušná metoda binární data před konverzí zkomprimuje.
Třídu Base64Converter
(je součástí přiložených příkladů) můžeme brát jako nějakou servisní službu, jejíž implementací se zabývat detailně nebudeme, pro případné zájemce však je zdrojový kód k dispozici.
Parsování textu
Identifikátor každého tlačítka se skládá se znaku "c"
(znak je odvozen z anglického ekvivalentu slova buňka – cell), za kterým následuje bezprostředně jeho pořadové číslo.
Abychom z takového identifikátoru získali zpět číslo, využijeme statickou metodu třídy Integer
, nejdříve však je třeba odstranit ten první znak, k čemuž použijeme metodu substring()
…
int pointer = Integer.parseInt(param.substring(1));
Pokud parser z nějakého důvodu číslo získat neumí, vyhodí výjimku. Užitečné metody pro parsování textů nabízí všechny třídy objektových alternativ k primitivním datovým typům.
Class – třída pro popis tříd
Již jsme zmínili dříve, že každá třída jazyka Java má vlastnosti jako atributy, metody, konstruktor, ale třeba i třídu předka nebo název balíčku.
Popis vlastností každé třídy (meta-model) je k dispozici (za běhu programu) prostřednictvím objektů typu Class
.
Jeho instanci získáme zápisem třídy, který je doplněný tečkou a klíčovým slovem class
, úplný název třídy (pro zobrazení či tisk) získáme metodou toString()
.
Podívejme se na příklad získání takového objektu.
Class<BoardServlet> myClass = BoardServlet.class; (1)
String className = myClass.getName(); (2)
1 | Objekt typu Class získáme uvedením názvu třídy doplněného class .
Pokud máme k dispozici objekt, jeho třídu lze získat (za běhu programu) metodou getClass() . |
2 | Ukázka získání úplného jména třídy (včetně balíčku). |
Logování událostí
Pokud dojde při zpracování k nějaké nestandardní události, je vhodné uživatele informovat v grafickém rozhraní, ale pro zpětné hledání příčiny je třeba zapisovat také okolnosti vzniku včetně časového razítka a naléhavosti.
Zápis chyb a dalších událostí lze provádět do textových souborů, které se nazývají logy.
Popis hierarchie volání metod se běžně označuje anglickým slovem stacktrace.
Logování ve zdrojovém kódu se provádí pomocí třídy Logger
, do jejího konstruktoru budeme zapisovat úplný název třídy v textovém tvaru.
Typický příklad použití následuje.
Logger LOGGER = Logger.getLogger(BoardServlet.class.getName());
try {
callSomeService();
} catch (Exception e) {
LOGGER.log(Level.WARNING, "An error processing parameters", e);
}
Instance typu Logger
se často zapisuje jako privátní statický atribut
třídy.
Umístění logu závisí na nastavení projektu.
Nově získanou znalost uplatníme v metodě doGet()
, kde zachycenou výjimku můžeme logovat (zapisovat do souboru logů nebo do jiného úložiště).
Třída Optional
Podívejme se na ukázku použití lambda výrazu pro proměnnou typu Consumer
.
Už dříve jsme zmiňovali problém volání metody na objektu s hodnotou null
, při které dojde k přerušení programu vyhozením výjimky.
Jedním z možných řešení je vyrobit objektovou obálku s generickým typem výsledku pro uschování původní hodnoty.
Konkrétní řešení nabízí standardní Java knihovna (od verze 8) v podobě třídy Optional
– ukažme si její použití.
Optional<String> text = Optional.ofNullable("text");
Metoda ofNullable()
je tovární metoda, která nám vyrobí hodnotu typu Optional
.
V okamžiku, kdy chceme takovou hodnotu zpracovat, zavoláme metodu text.ifPresent(consumer)
podle vzoru:
text.ifPresent((String t) -> doSomething(t));
Do metody se posílá instance třídy Consumer
zapsaná pomocí lambda výrazu, ale k volání metody doSomething()
(s parametrem textu) dojde jen v případě, že jeho hodnota je různá od null
.
Přesně takové využití najdeme v metodě doGet()
, která zapisuje do HTML modelu zprávu, pokud je různá od null
.
Tento zápis se vejde na jediný řádek.
Je však pravda, že kódování pomocí lambda výrazů vyžaduje určitou zkušenost, a proto v našich příkladech využijeme podobné konstrukce jen zřídka.
Závěrem proveďme ve zdrojovém kódu vlastní změny: rozšiřme naši znakovou sadu o několik nových znaků, můžeme doplnit třeba vykřičník, otazník a některé číslice.
4.5.2. Binární podoba textu
Připomeňme si, že pojem znaková sada představuje číslovaný seznam vybraných grafických znaků či piktogramů.
Příkladem mohou být znaky národní abecedy, čísla, mezery, znak odřádkování, symboly matematických operací a mnoho dalších.
Text se vytváří seskupením pořadových čísel těchto znaků a v jazyce Java se k tomu hodí nejlépe třída String
.
Připomeňme si ještě, že každý primitivní datový typ má svoji pevnou délku uváděnou v bitech, což je nejmenší jednotka informace se dvěma stavy.
V této kapitole si vyzkoušíme, jak by vypadal zápis textu v binární podobě ve znakové sadě, která obsahuje pouze 4 bity.
Zadání našeho dalšího úkolu pak zní: vytvořme HTML formulář pro simulaci bitového pole pro šest znaků. Každý znak bude mít (pro zjednodušení) pouze 4 bity, což vede ke stručné znakové tabulce obsahující 16 znaků (24). První znak bude obsahovat podtržítko, další znaky budou velká písmena z počátku abecedy.

Pro lepší představu si takovou zkrácenou sadu znaků popíšeme:
Index | Znak | Binární číslo | Poznámka |
---|---|---|---|
0 |
_ |
0000 |
Jako oddělovač slov použijeme podtržítko. |
1 |
A |
0001 |
Druhý znak naší znakové sady. |
2 |
B |
0010 |
|
3 |
C |
0011 |
|
4 |
D |
0100 |
|
5 |
E |
0101 |
|
6 |
F |
0110 |
|
7 |
G |
0111 |
|
8 |
H |
1000 |
|
9 |
I |
1001 |
|
10 |
J |
1010 |
|
11 |
K |
1011 |
|
12 |
L |
1100 |
|
13 |
M |
1101 |
|
14 |
N |
1110 |
|
15 |
O |
1111 |
Poslední znak naší znakové sady. |
Řešení
Implementaci postavíme opět na předchozím servletu, který upravíme podle zadání, bude se nám hodit také třída pro objektový model mřížky.
Pro implementaci úkolu by se nám hodilo, kdyby datový model z předchozího řešení obsahoval navíc seznam znaků požadované sady a nabídl další metody:
-
Metodu pro získání znaku pro buňku tabulky.
-
Metody pro získání znaku na daném indexu ve znakové sadě.
-
Velikost znakové sady.
Za tímto účelem vytvoříme novou třídu ExtendedBoardModel
, která bude potomkem té původní třídy SimpleBoardModel
.
Původní třída přitom zůstane beze změny.
Pole znaků budeme držet v atributu charset
, vytvoření pole jsme už zmiňovali.
Zajímavá metoda getCharacterId()
slouží pro výpočet hodnoty znaku: algoritmus projde postupně čtyři sousední bitové pozice a na základě hodnoty vypočítá index znakové sady.
V případě třídy StringBuilderServlet
zmíníme hlavně vytvoření tabulkového modelu.
V tabulce znakové sady chceme zobrazit také binární podobu čísla s vedoucími nulami před číslem, v jazyce Java však metoda String.format()
binární formátování nepodporuje.
Řešení existuje více, my si zde pomůžeme doplněním některých bitů.
public Object[][] getTableModel(ExtendedBoardModel boardModel) {
int mask = (int) Math.pow(2, ExtendedBoardModel.BITS_PER_CHAR); (1)
Object[][] result = new Object[boardModel.getCharsetSize() + 1][];
result[0] = new Object[] {"Number", "Character", "Binary"};
for (int i = 0; i < boardModel.getCharsetSize(); i++) {
result[i + 1] = new Object[]
{ i
, boardModel.getCharOfSet(i)
, Integer.toBinaryString(i | mask).substring(1) (2)
};
}
return result;
}
1 | Nejdříve si připravíme číslo, jehož bitová hodnota bude o jedničku větší než maximální hodnota čísla čtyřbitového rozsahu. |
2 | Operátor | (česky svislík, svislítko, svislice, kolmítko, roura, anglicky pipe) provádí bitový součet dvou čísel, což znamená, že do výsledku promítne logický součet bitů na stejné pozici.
Je pravda, že náš výsledek bude o jeden znak delší, ale správnou hodnotu získáme snadno metodou substring() .
Pro úplnost uveďme také doplňkový operátor & pro bitový součin, který provádí logický součin bitů. |
Závěrem proveďme ve zdrojovém kódu vlastní změny: rozsah znakové sady rozšiřme o jeden bit tak, aby sada pojala dvojnásobný počet znaků.
Dále připravme model na vkládání znakové sady konstruktorem, přitom do konstruktoru vložíme kontrolu velikosti znakové sady, která bude vyhazovat výjimku typu IllegalArgumentException
.
4.5.3. Piškvorky s defenzivní strategií hry
Hra Piškvorky patří mezi strategické hry, jejíž kořeny sahají hluboko do historie. Existuje několik variant, mezi nejznámější patří zřejmě hra Gomoku s alternativním názvem 5 in row (v překladu 5 v řadě). Naše pravidla hry budou následující: hráči kladou střídavě barevné kameny do mřížky s konečnou velikostí, každý hráč má svoji barvu. Vítězem se stává hráč, který jako první postaví nepřerušenou řadu minimálně pěti kamenů své barvy, povolena je i řada šikmá. Hru je možné kdykoli resetovat tlačítkem a zahájit hru novou. V této implementaci bude protihráčem vždy počítač, výhodu prvního tahu dostane vždy člověk.

Strategie SW protihráče bude pouze jednoduchá obrana.
Cílem hry totiž není vytvořit neporazitelný algoritmus, ale především ukázat řešení vhodné pro další studium a později třeba i pro implementaci vlastních nápadů.
Čtenáři mohou nad rámec této knihy vyhledat některou knihovnu zaměřenou na hry typu Gomoku
a zkusit napojit její algoritmy do stávající architektury.
Diagram tříd
Začněme diagramem tříd:
Na první pohled si můžeme všimnout nového typu objektu, který má nad svým názvem uveden stereotyp zvaný enumeration a místo atributů má statické konstanty (poznáme je podle velkých písmen). Nejdříve se podívejme na schematický popis zpracování HTTP požadavku, budeme pokračovat popisem enumerátorů a jejich možností a na konci kapitoly najdeme podrobnější popis tříd a jejich metod.
Jak to funguje?
Popis zpracování HTTP požadavku krok po kroku:
-
Servlet dostane
HTTP
požadavek metodouGET
neboPOST
a zavolá službu, která provede následující akce:-
obnoví původní datový model deskové hry,
-
přidá do něj poslední tah hráče,
-
pokud tah vede k vítězství, stav se zapíše do modelu a metoda končí,
-
vypočítá tah počítače a přidá ho do modelu,
-
pokud tah vede k vítězství, stav se zapíše do modelu a metoda končí,
-
jinak zpracování pokračuje standardním způsobem.
-
-
Sestaví se HTML model hrací desky (pomocí třídy
Element
pro generování HTML kódu) – podle aktuálního modelu hrací desky. -
Doplníme standardní patičku stránky a kořenový element uzavřeme.
Obecným cílem defenzivního algoritmu je najít (na desce) a obsadit (svým kamenem) strategické místo protihráče, v tomto případě člověka.
Následuje popis algoritmu, jehož implementace začíná voláním metody BoardService.mashineMoves(board)
.
-
Metoda si načte parametry z modelu hrací desky a volá jinou metodu (stejné třídy) s názvem
getNextPoint()
. -
Tato metoda obsahuje tři vnořené cykly, ve kterých hledá nejdelší nepřerušenou řadu kamenů protihráče (člověka):
-
Nejdříve hledáme kameny v délce kratší o jeden kámen, než je vítězný počet. V našem případě začneme tedy hodnotou
4
(čtyři). Pokud se taková řada nenajde, snížíme podmínku o jeden kámen a hledáme znovu, až se dostaneme k číslu2
(dva). -
Směry položených kamenů jsou popsány výčtovým typem
DirectionEnum
. Kameny hledáme metodougetDirectionBorder()
a pro rychlejší zpracování budeme prohledávat jen úsečky procházející posledním vloženým kamenem. Pokud metoda hledanou sekvenci kamenů najde, vrátí dva okrajové body vně této pomyslné úsečky, jinak vracínull
. -
Poslední cyklus projde body nalezené hranice, a vrátí první, který leží uvnitř desky a zároveň je volný.
-
-
Pokud se nepodaří najít žádnou hranici, vezmou se všechny volné body kolem posledního tahu a náhodným výběrem se vybere jeden z nich.
Brzy však zjistíme, že k vítězství stačí jen 6 tahů, a hra přestane být zajímavá.
Z toho důvodu rozšíříme algoritmus pomocí metody ExtendedBoardService.getNextPoint()
, která používá odlišnou strategii, náhodně však deleguje své zpracování i na rodičovskou implementaci pomocí klíčového slova super
.
Takové řešení ukazuje využití Polymorfismu
.
Protože nová strategie je už poměrně invazivní a málo zábavná pro hráče, použijeme tento algoritmus jen pro polovinu případů, a to náhodně.
V překrytné metodě lze volat zastíněnou metodu pouze do nejbližšího předka třídy. Pro volání vzdálenějších předků nemá jazyk Java podporu. Tyto případy je třeba řešit vhodnějším návrhem tříd. |
Jak funguje rozšířená implementace metody getNextPoint()
?
-
Metoda projde postupně všechna volná pole kolem posledního tahu protihráče.
-
Pro každé takové pole naklonujeme hrací desku (metodou
board.cloneBoard()
). -
Umístí do něj (na zkoušku) kámen protihráče a zkontroluje, zdali takový tah nevede k vítězství protihráče, či k souvislé řadě 4 po sobě jdoucích kamenů.
-
Pokud ano, obsadí ho vlastním kamenem (počítače).
-
Jinak zavolá tu původní implementaci předka.
Enumerátor
Při programování je někdy výhodné pracovat s programovými objekty, které reprezentují nějaký neměnný seznam hodnot (říkáme jím konstanty), typickým příkladem je seznam měsíců kalendářního roku.
Pro takové případy nabízí jazyk Java výčtový typ (anglicky enumerated type) zvaný též enumerátor.
Enumerátor je specifický objektový typ, který poskytuje pevný (neměnný) seznam instancí (konstant) svého vlastního typu.
Každá konstanta enumerátoru poskytuje (prostřednictvím svých metod) jméno, pořadové číslo (index) a také seznam všech konstant stejného typu.
Všechny enumerátory mají společného rodiče, kterého tvoří abstraktní třída Enum
, její implementace pak mohou mít (podobně jako běžné třídy) své atributy, konstruktory i metody.
S výčtovými typy pak lze pracovat podobně, jako by se jednalo o statické konstanty.
Podívejme se na zdrojový kód výčtového typu.
public enum StoneEnum { (1)
WHITE_STONE, (2)
BLACK_STONE, (3)
NO_STONE; (4)
}
1 | Hlavička se podobá třídě, deklarace však obsahuje klíčové slovo enum . |
2 | První hodnota reprezentuje bílý kámen na hrací desce. |
3 | Druhá hodnota reprezentuje černý kámen na hrací desce, jednotlivé položky se oddělují čárkou. |
4 | Poslední konstanta se zakončuje středníkem. |
Pro srovnání zkusme implementovat podobný enumerátor pomocí standardních prostředků běžné třídy. Na tomto náhradním řešení může být chování výčtových typů a jejich přínos jasnější.
public final class MyStoneEnum { (1)
public static final MyStoneEnum WHITE_STONE
= new MyStoneEnum(0, "WHITE_STONE"); (2)
public static final MyStoneEnum BLACK_STONE
= new MyStoneEnum(1, "BLACK_STONE");
public static final MyStoneEnum NO_STONE
= new MyStoneEnum(2, "NO_STONE");
private final int order; (3)
private final String name; (4)
private MyStoneEnum(int order, String name) { (5)
this.order = order; (6)
this.name = name;
}
public int ordinal() {
return order;
}
public String getName() {
return name;
}
public static MyStoneEnum[] values () { (7)
MyStoneEnum[] result = { WHITE_STONE, BLACK_STONE, NO_STONE };
return result;
}
public static MyStoneEnum valueOf(String name)
throws IllegalArgumentException {
for (MyStoneEnum value : values()) { (8)
if (value.getName().equals(name)) {
return value;
}
}
throw new IllegalArgumentException(name);
}
}
1 | Modifikátor třídy final znemožní tvorbu potomků. |
2 | Veřejný statický atribut je stejného typu jako třída, modifikátor final garantuje jeho neměnnost. |
3 | Pořadové číslo začíná hodnotou 0 . |
4 | Jméno výčtu vždy koresponduje s názvem statického atributu. |
5 | Konstruktor je soukromý, aby nové instance nemohla vytvářet žádná jiná třída. |
6 | Klíčové slovo this odlišuje název atributu od stejnojmenného argumentu. |
7 | Metoda vrací všechny své statické atributy jako pole. |
8 | Metoda hledá statický atribut třídy podle jména a v případě neúspěchu vyhodí výjimku. |
Snad už je teď jasnější, že zavedení výčtových typů do jazyka Java přineslo zjednodušení zdrojového kódu pro podobné případy.
Enumerátory však lze efektivně používat také v příkazech switch
.
Příkaz switch
Příkaz switch
umožňuje větvit program podle primitivních celočíselných hodnot (literálů či konstant), z objektů jsou podporovány ještě typy String
a Enum
.
Přikládám zkrácenou ukázku použití ze třídy BoardModel
.
/** Set a stone by an index */
public void setStone(BoardPoint point, StoneEnum stone) {
switch (stone) { (1)
case BLACK_STONE: (2)
case WHITE_STONE:
fields.set(getArayIndex(point, stone)); (3)
lastMove.set(point);
break; (4)
default: (5)
throw new IllegalArgumentException("Illegal stone" + stone);
}
}
1 | Příkaz switch hodnotí proměnnou, která musí být různá od null , tělo příkazu je ohraničené složenými závorkami. |
2 | Jednotlivé případy se odchytávají pomocí klíčového slova case s konstantou zakončenou dvojtečkou.
Výčtový typ je jednoznačně určen v hlavičce příkazu a při zachytávání případů se už neuvádí. |
3 | Následuje blok příkazů, které se provádějí až do nalezení klíčového slova break přes další definice case .
V tomto bloku se tedy kameny obou barev zpracují shodně. |
4 | Příkaz na ukončení bloku příkazů; v případě jeho absence by zpracování pokračovalo dále. |
5 | Hodnoty, které nejsou zachyceny žádným případem case , lze zachytit (volitelně) klíčovým slovem default , přitom tento blok nemusí být nutně na poslední pozici.
Tady se vyhazuje výjimka, protože zadanému bodu má smysl přiřadit kámen pouze bílé nebo černé barvy. |
Popis tříd diagramu a jejich metod
-
GoMokuServlet
je třída typuHttpServlet
, která nijak nevybočuje z předchozích ukázek. Třída obsahuje dvě statické konstanty, první obsahuje minimální počet kamenů nutných k vítězství, druhá obsahuje seznam kaskádových stylů HTML stránky. -
StoneEnum
poskytuje konečný výčet stavů (kamenů) každé buňky na hrací desce. Jedna buňka může obsahovat kámen bílý, černý, anebo může být prázdná. Poznámky k výčtovému typu najdeme v kapitole Enumerátor. -
BoardPoint
je třída pro adresování jednoho bodu hrací desky (buňky), kterou budou využívat ostatní třídy. Třída podporuje jak souřadnicové adresování (x
ay
) tak i adresování jednorozměrné (index
), dále nabízí metody pro posun bodu (na desce) a pro výpočet vzdálenosti od jiného bodu. -
BoardModel
je třída pro sestavení datového modelu hrací desky. Hlavní změnou proti předchozím příkladům jsou tři stavy buňky, protože každá buňka může obsahovat právě jednu ze tří hodnot výčtového typuStoneEnum
. Pro popis modelu využijeme opět tříduBitSet
, ale pro každou buňku budeme rezervovat dva bity s následujícími stavy: oba bity s hodnotou0
(nula) značí prázdnou buňku, první bit (ten s menším indexem) s hodnotu1
značí bílý kámen, jinak druhý bit s hodnotou1
značí černý kámen. Případ se dvěma jedničkami je neplatný.
Popis vybraných metod:-
setStone(boardPoint, stone)
– metoda umístí kámen typuStoneEnum
na zadanou pozici, -
getStone(boardPoint)
– metoda vrací kámen (typuStoneEnum
) na zadané pozici, -
getOpponentStone()
– metoda vrací kámen protihráče, -
getBitSetIndex()
– privátní metoda na výpočet indexu pro objekt typuBitSet
, která fyzicky obsahuje požadované stavy, -
isInsideBoard(point)
– metoda zjistí, zdali se bod nachází uvnitř hrací desky, -
hasStone(point, stone)
– metoda kontroluje shodný kámen na zadané pozici, -
getLastMove()
– metoda vrací souřadnici posledního tahu v datovém objektuBoardPoint
, -
gameOver()
– dojde k logickému ukončení hry, pokus o vložení dalšího kamene vyhazuje výjimku, -
isGamewOver()
– vrací příznak ukončení hry, -
getErrorMessage()
je pomocná metoda, pomocí které lze získat zprávu určenou pro HTML stránku. Třída tyto zprávy nevytváří, ale lze je zapsat veřejnou metodousetErrorMessage()
, -
cloneBoard()
– metoda vytvoří kopii hrací desky, kterou lze použít pro simulování tahu počítače za účelem jeho vyhodnocení. Změny stavu klonované desky nesmí ovlivnit původní model.
-
-
BoardService
– třída poskytuje aplikační logiku, což jsou algoritmy potřebné pro běh hry. Jejich vyčlenění do samostatné servisní třídy zlepšuje čitelnost kódu, usnadňuje jeho údržbu a usnadňuje sdílení společných funkcí i jeho případné rozšiřování. Opět si popíšeme význam metod:-
createBoardModel()
je tovární metoda na výrobu datového modelu hrací desky, definuje jeho rozměry a nastaví poslední tah z parametru HTTP requestu, -
closeGameOnVictory()
– pokud jsou splněny podmínky vítězství na základní desce, dojde k uzavření modelu hrací desky, -
isWinner()
– metoda dostane parametrem model desky a minimální počet kamenů v řadě potřebný k vítězství. Metoda vždy testuje jen blízké okolí posledního vloženého kamene, -
mashineMoves()
– zpracování tahu počítače, -
getWinnerMessage()
– metoda sestavuje zprávu pro vítěze hry, -
getDirectionBorder()
– metoda vrací dvojici bodů na přímce, které ohraničují počet kamenů v daném směru, jinak vrací hodnotunull
, -
of()
– tovární metoda na výrobu instance služby.
-
-
ExtendedBoardService
– třída překrývá metodu za účelem implementace odlišné strategie, řešení využíváPolymorfismus
. -
DirectionEnum
– opět se jedná oenumerátor
, který nabízí výčet směrů kamenů, které se počítají pro vítězství:-
move()
– enumerátor nabízí jedinou metodu, která umožňuje posun bodu (zadaného parametrem) v daném směru – jedním nebo druhým směrem. Pro možnost řetězení metoda vrací stejnou instanci bodu, kterou dostala parametrem.
-
Zpracování dotazu
Řešení kódu naznačíme jen na úrovni vstupní metody servletu doGet()
…
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
try (HtmlElement html = HtmlElement.niceOf(
"Go-moku (simple defensive strategy)", response, CSS)) {
html.getBody().addHeading(html.getTitle());
try {
BoardModel board = service.createBoardModel(request); (1)
Element form = createFormElement(html.getBody(), board); (2)
board.getErrorMessage()
.ifPresent(msg -> form.addSpan("msg").addText(msg)); (3)
service.getWinnerMessage(board)
.ifPresent(msg -> form.addSpan("msg").addText(msg));
createMainBoard(form, board); (4)
createResetButton(form, "New game");
} catch (Exception e) { (5)
String msg = "Internal server error caused by "
+ e.getClass().getSimpleName();
LOGGER.log(Level.SEVERE, msg, e);
html.getBody().addSpan("msg").addText(msg
+ ", see a{nbsp}server log for detail");
}
WebTools.addFooter(html, this);
}
}
1 | Metoda obnoví (z parametrů requestu) poslední datový model hrací desky a přidá do něj poslední tah hráče i nový tah počítače, případně nastaví i informace o stavu hry. |
2 | Metoda sestaví HTML model hrací desky. |
3 | Podmínečné zobrazení chybové zprávy s využitím třídy Optional , podobnou konstrukci použijeme na dalším řádku pro zobrazení zprávy o vítězství. |
4 | Sestavíme model hrací desky a na dalším řádku přidáme resetovací tlačítko. |
5 | Případnou výjimku zpracování zachytíme za účelem archivace této události do logu a informování uživatele. |
Úplné řešení najdeme v přiložených příkladech. Jejich obsah může být zajímavý z pohledu algoritmu, ale z pohledu syntaxe jazyka už by neměl přinést významné překvapení. V případě nejasností k API knihovny Java doporučuji použít dokumentaci JavaDoc [5]. Pokud budete chtít sledovat chování metod a obsah proměnných, lze do zdrojových kódů vkládat logovací zprávy. Alternativou je krokování aplikace v pokročilém editoru (IDE).
Automatizované testy
Když napíšeme algoritmus, je vhodné také ověřit, že funguje podle očekávání. Pokud máme k dispozici grafické rozhraní, nabízí se vyzkoušet algoritmus prostřednictvím tohoto rozhraní. V případě velkého množství tříd je však pracné kontrolovat všechny třídy opakovaně, přitom chybu lze zanést kdykoli později při nějaké zdánlivě banální úpravě. Z tohoto důvodu se osvědčilo psát programový kód, který správnou činnost vybraných algoritmů zkontroluje při každém sestavení projektu. Testovací kód tvoří opět Java třídy uložené ve struktuře projektu. Konvence pojmenování testovací třídy se odvozuje od třídy testované, pro odlišení se připojuje sufix Test. Testovací třídy bývají ve stejném Java balíku, protože však nemá význam přikládat je do výsledné aplikace, jsou uloženy v oddělené adresářové struktuře. Při takzvaných jednotkových testech (anglicky unit tests) se eliminuje závislost na službách jiných tříd a externích systémů – včetně síťových přenosů. Podívejme se na ukázku jednoduchého testu využívajícího knihovnu jUnit [24].
@Test
public void testPointIndex() { (1)
int x = 1;
int y = 2;
BoardPoint point = new BoardPoint(x, y); (2)
BoardModel board = new BoardModel(3, 3, null);
int index = point.getPointIndex(board); (3)
Assert.assertEquals(7, index); (4)
}
1 | Metoda určená k testování je veřejná a označuje se anotací @Test z knihovny jUnit. |
2 | Sestavíme objekt testování, v tomhle případě je to bod na hrací desce určený souřadnicovým systémem. |
3 | Test kontroluje přepočet souřadnic na index. Protože metoda pro přepočet potřebuje znát velikost hrací desky, vytvoříme ji na předchozím řádku. |
4 | Sestavíme tvrzení, že bod na souřadnici (x=1, y=2) bude mít na desce o velikosti 3 x 3 pole pořadové číslo (index) 7. Při neshodě metoda vyhodí výjimku. Třída pochází z knihovny jUnit. |
Testy tedy kontrolují, že chování třídy či metody je v souladu s jejím popisem (ideálně v JavaDoc).
Spouštění automatizovaných testů je podporováno nástrojem Maven (příkazem mvn test
).
Při výchozím nastavení Maven se testy spouštějí automaticky při každém sestavení projektu.
Pro další studium můžete vyzkoušet tento zdroj [24].
Optimalizace datových toků
Z předchozího popisu je zřejmé, že každý tah hráče vede k překreslení celé HTML stránky. Velké objemy datových přenosů však mohou zbytečně zatěžovat (internetovou) síť a zpomalovat rychlost odezvy. Má to nějaké řešení?
-
Nejjednodušší cestou může být odstranění formátovacích bílých znaků mezi HTML elementy. V našich příkladech tyto znaky lze vyloučit použitím alternativní metody pro sestavení kořenového elementu: namísto stávající metody
HtmlElement.niceOf()
použijeme metoduHtmlElement.of()
. Pomocí konfigurační třídyDefaultHtmlConfig
lze odstranit také zalamování řádků, tento zásah však bude mít menší vliv na výslednou velikost. -
Další možností je použití komprese datových přenosů, kterou lze nastavit na straně webového kontejneru (třeba Tomcat).
-
Obecným trendem je využití technologie AJAX, která předpokládá aktivní podporu JavaScriptu na straně internetového prohlížeče. Při komunikaci se serverem si JavaScript vyžádá pouze nezbytná data, což může být jen malá část HTML kódu. Původní HTML kód JavaScript odstraní a nahradí ho novým – bez nutnosti načtení celé stránky.
-
Všechny výše uvedené tipy lze vzájemně kombinovat.
5. Jak psát dobrý kód
Smyslem studia není to, aby byl člověk chytřejší, ale aby měl větší požitek ze života.
Toto je téma na samostatnou knihu, přitom je vhodné zmínit, že dobrá aplikace začíná dobrým návrhem. Při návrhu tříd je užitečné mít na paměti, že náklady na vývoj SW aplikace mohou tvořit jen malou část celkových nákladů na projekt, tu větší část může spotřebovat údržba projektu a implementace dodatečných požadavků ještě po mnoho let po uvedení do produkčního provozu, a proto by měla mít dobrá přehlednost a srozumitelnost zdrojového kódu vysokou prioritu. Podívejme se na některá obecná doporučení:
-
Pišme komentáře. Dokumentace metod a tříd ve formátu JavaDoc by měla obsahovat způsob jejich použití z pohledu programátora. Zde můžeme shledat podobnost s návodem k použití a údržbě vysavače, kde nového majitele bude zajímat základní obsluha přístroje pravděpodobně více nežli popis rotujícího elektromagnetického pole jeho motoru.
-
Metody jedné třídy by měly pokrývat pouze jeden typ činnosti na své úrovni abstrakce, což znamená, že pro zpracování detailů chceme volat spíše metody jiných tříd. V případě implementace receptu na přípravu míchaných vajec (z počátku knihy) popíšeme algoritmus vaření pokrmu v jiné třídě než algoritmus nákupu chybějících potravin. Třídu pro nákup potravin pak můžeme využít při programování jiných receptů.
-
Duplicitní zdrojový kód je užitečné minimalizovat pomocí dědičnosti objektů, kompozice a užitečných knihoven, nebo kombinací uvedeného.
-
Pišme včas automatizované testy. Zejména jednotkové testy (anglicky unit tests) snižují riziko kumulace mnoha funkcí v metodě a mívají značně pozitivní vliv na návrh tříd.
-
I když poslední doporučení je trochu kontroverzní, dle mého názoru také přispívá k lepší strukturovanosti a čitelnosti zdrojového kódu ve většině případů: každá skupina příkazů vyžadující komentář si zaslouží vlastní metodu s popisem JavaDoc.
Zájemcům o toto téma doporučuji hledat na internetu pojem návrhové vzory [12]. Vývojářům grafických aplikací pak doporučuji nepřehlédnout architektonický návrhový vzor zvaný Model-View-Controller.
Závěrem snad ještě krátká poznámka k optimalizaci zdrojového kódu, která může zabránit zbytečným škodám. Ke spontánní optimalizaci kódu svádí často dobře míněná snaha o zvýšení výkonu. Protože však zásah do fungujícího algoritmu může mít i vážné negativní dopady, je vhodné zachovat vždy určitou obezřetnost.
-
Doporučuji zvážit předem všechny výhody, rizika a náklady.
-
Před zahájením optimalizace by měl být původní algoritmus pokryt jednotkovými testy, které budou garantovat původní chování.
-
Kontraproduktivní může být každá optimalizace, která s sebou nese zhoršení čitelnosti zdrojového kódu.
Pokud jste dočetli knihu až sem, přeji vám hodně úspěchů při uplatnění získaných znalostí. Dovolte mi připomenout, že jakékoli postřehy, dojmy a technické poznámky k obsahu této knihy jsou vítány, kontakt najdete na domovské stránce knihy [1]. Díky moc za ně.

Reference a zdroje pro další studium
Většina uvedených zdrojů je v angličtině.
-
[1] – Domovská stránka této knihy:
https://jbook.ponec.net/cs.html -
[2] – Odkaz ke stažení příkladů ke knize:
https://pponec.github.io/jbook/download-1.0.0.html -
[3] – Java Language and Virtual Machine Specifications – Oracle © Corporation:
https://docs.oracle.com/javase/specs/ -
[4] – Domovská stránka OpenJDK:
https://openjdk.java.net/ -
[5] – Dokumentace programového rozhraní Java 11 ve formátu JavaDoc:
https://docs.oracle.com/en/java/javase/11/docs/api/ -
[6] – TIOBE community index for measure of popularity of programming languages:
https://www.tiobe.com/tiobe-index/ -
[7] – Apache Maven in 5 Minutes – The Apache Software Foundation:
https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html
Příklady byly odladěny na Maven, verze 3. -
[8] – Netbeans IDE:
https://netbeans.apache.org/download/ -
[9] – Knihovna volně šiřitelných obrázků:
https://pixabay.com/. -
[10] – Česká Wikipedie:
https://cs.wikipedia.org/ -
[11] – List of Unicode characters:
https://en.wikipedia.org/wiki/List_of_Unicode_characters -
[12] – Návrhový vzor:
https://cs.wikipedia.org/wiki/N%C3%A1vrhov%C3%BD_vzor -
[13] – Architektonický návrhový vzor Model-View-Controller:
https://cs.wikipedia.org/wiki/Model-view-controller -
[14] – The Java 8 Stream API Tutorial:
https://www.baeldung.com/java-8-streams -
[15] – Question and answer site Stack Overflow:
https://stackoverflow.com/ -
[16] – Webová stránka na prohledávání repozitáře Maven:
https://search.maven.org/ -
[17] – Domovská stránka Linuxu Xubuntu:
https://xubuntu.org/ -
[18] – Top Programing Languages 2018:
https://medium.com/sfl-newsroom/java-javascript-kotlin-and-more-2018-stats-for-development-languages-de1a961e4c38 -
[19] – Introduction to Java Primitives:
https://www.baeldung.com/java-primitives -
[20] – HTML Tutorial of w3schools.com:
https://www.w3schools.com/html/ -
[21] – CSS Tutorial of w3schools.com:
https://www.w3schools.com/css/ -
[22] – Java operators:
https://www.w3schools.com/java/java_operators.asp -
[23] – Domovská stránka Java knihovny Ujorm:
https://ujorm.org/ -
[24] – JUnit Tutorial for Beginners: Learn in 3 Days :
https://www.guru99.com/junit-tutorial.html -
[25] – Apache Software Foundation (ASF) :
https://www.apache.org/foundation/ -
[25] – Bootstrap, an open source toolkit for developing with HTML, CSS, and JS:
https://getbootstrap.com/ -
[26] – Java Language Keywords:
https://docs.oracle.com/javase/tutorial/java/nutsandbolts/_keywords.html -
[27] – Regulární výrazy v JavaDoc:
https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/regex/Pattern.html -
[28] – Virtualization application Docker®:
https://www.docker.com/ -
[29] – Security-oriented, lightweight Linux distribution Alpine Linux:
https://alpinelinux.org/