Dynamické programovací jazyky Groovy Mnohé příklady jsou převzaty z knihy Groovy in Action a nebo jsou touto knihou silně inspirovány Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
1 z 133
Obsah 1. Předehra: Java × Groovy 2. Groovy – rychlý úvod do jazyka 3. Hlubší pohled na práci s daty 4. Uzávěry 5. Dynamická podstata jazyka 6. KONEC Je potřeba prověřit: 3.2.2 V programu nefunguje anotace @Field 3.6.5 V programu zlobí příkaz def (small, big) = list.split { it < 2 }
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
2 z 133
1.
Předehra: Java × Groovy
Obsah 1.1 1.2 1.3 1.4 1.5 1.6 1.7 1.8 1.9
Proč nový jazyk Dynamické jazyky – charakteristika Skriptovací jazyky Koncepce jazyka Groovy Dva způsoby zavedení třídy Přehled vlastnosti jazyka Groovy Jak Groovy získat Co Groovy od Javy přebírá Základní zjednodušení 1.9.1 Skripty 1.9.2 Podmínky 1.9.3 Sjednocení způsobů zjištění počtu prvků v kontejneru 1.10 Co dalšího je jinak 1.11 Co Java má a Groovy (zatím) ne
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
3 z 133
1.1 Proč nový jazyk ► Hodně se hovoří o tzv. dynamických jazycích,
které jsou populární zejména mezi začínajícími programátory
► Jejich zastánci si občas nepřipouštějí jejich nevýhody ● Nemožnost automatické refaktorace ● Omezená hlídání překladače, které programátoři „zapomínají“ vyvažovat testy ● Dynamicky charakter jazyka vyžaduje jistou režii, která výsledný program výrazně zpomaluje (v závislosti na aktuálních operacích 4× až 20×) ► Velké tlaky na úpravu (= zdynamičtění) Javy ● Takovéto úpravy však jazyk zbytečně nafukují a přitom zeslabují jeho robustnost ● Při zařazování nových funkcí se často objevují závažné problémy se zpětnou kompatibilitou ► Pro programování však není důležitý jazyk, ale platforma =>
bylo by vhodné vybudovat na danou platformou jazyk, který by: ● Podporoval nově se prosazující programovací techniky ● Bezproblémově spolupracoval s doposud vyvinutými programy
► Existuje řada nejrůznějších jazyků postavených nad platformou Java –
stačí navrhnout syntaxi a překladač i knihovnu poskytne platforma ● Ceylon, Closure, Groovy, JRuby, Jython, Scala, …
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
4 z 133
1.2 Dynamické jazyky – charakteristika ► Dynamické jazyky nabízejí možnost provádět za běhu operace,
které statické jazyky umožňují pouze v době překladu ● ● ● ●
Měnit strukturu objektů Upravovat definice datových typů, např. přidávat atributy a metody Modifikovat chování objektů změnou definice těla metody Spouštět části programu definované až za běhu
► Definice dynamických jazyků není jednoznačná,
protože se snaží rozlišovat mezi kódem a daty a mezi dobou překladu programu a dobou jeho běhu ● Virtuální stroje, just-in-time překlad a schopnosti některých jazyků modifikovat kód programu za běhu činí toto dělením poněkud abstraktním ● Míra dynamičnosti jazyka se proto většinou odvozuje od snadnosti, s jakou je možno realizovat výše zmíněné operace
► Řada lidí zaměňuje dynamické jazyky s dynamicky typovanými jazyky ● Podrobnosti o dynamickém typování – viz pasáž 3.1.2 Dynamické typování ● Podrobnosti o dynamické podstatě Groovy – viz pasáž 5. Dynamická podstata jazyka
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
5 z 133
1.3 Skriptovací jazyky ► Jako skriptovací jazyk označíme (většinou) interpretovaný programovací jazyk
sloužící k ovládání jiných aplikací
► Skriptovací jazyky dělíme do dvou skupin ● Jazyky se zabudovanou podporou v operačním systému, která umožňuje v tomto jazyce přímo zadávat příkazy operačnímu systému (např. VBScript, JScript) ● Jazyky ovládající vyhrazenou aplikaci či skupinu aplikací (např. JavaScript) ► Podpora skriptovacích jazyků v operačním systému může být několika druhů ● Je definována speciální aplikace, která po spuštění umožní zapisovat příkazy jazyka přímo z konzoly ● Operační systém akceptuje zdrojový kód v daném jazyku jako spustitelný program (= skript), přičemž po spuštění tohoto programu spustí OS příslušný interpret, který zadaný program přímo interpretuje ► Některé jazyky se současně používají jako skriptovací a univerzální ● jedny (např. Perl) vznikly původně jako skriptovací a časem se rozšířily na univerzální, ● jiné (např. Lisp) vznikly původně jako univerzální a časem vznikly jejich dialekty používané jako skriptovací (AutoLisp, ECMALisp), ● další (např. Groovy) byly od samého začátku navrženy pro obě použití Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
6 z 133
1.4 Koncepce jazyka Groovy ► Jazyk běží nad virtuálním strojem Javy a používá knihovnu Javy,
virtuální stroj nerozpozná, jestli byla třída napsaná v Javě nebo v Groovy ● Program může sestávat mixu z tříd napsaných v Javě a v Groovy
► Syntaxe Groovy je maximálně shodná s Javou, takže v optimálním případě
je možno změnit jazyk pouhou změnou přípony zdrojového souboru ● To výrazně usnadňuje přijetí jazyka programátory pracujícími v Javě
► Groovy je plnohodnotný dynamický jazyk s vlastnostmi inspirovanými jazyky
Smalltalk, Python, Dylan, Ruby, Perl
► Groovy se snaží maximalizovat
své vyjadřovací schopnosti a současně podobnost své syntaxe s Javou
► Autoři chtěli navíc vytvořit jazyk,
který by mohl být současně skriptovací ● V Groovy je možno velice efektivně psát dávkové soubory a různé skripty ovlivňující chování celého systému Převzato z Groovy in Action, str. 3, Fig. 1.1
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
7 z 133
► Groovy se nesnaží Javu nahradit, ale pouze doplnit =>
je zcela na vás, které části programu napíšete v Javě a které v Groovy ● Groovy-třídy mohou dědit z Java-tříd a naopak ● Programy v Groovy se překládají do standardních class-souborů; Groovy-kód lze zpětně přeložit dekompilátory Javy (nebude se ale originálu podobat a dost možná se v něm ani nevyznáte) ● Nástroje pro instrumentaci kódu akceptují i kód napsaný původně v Groovy
► Groovy je součástí ekosystému Javy –
byl pro něj definován Java Specification Request – JSR ● JSR-223: Scripting for the Java Platform ● JSR-241: The Groovy Programming Language
► Groovy je druhým standardním jazykem pro platformu Java ► Domovská stránka Groovy: http://groovy.codehaus.org/ ► Stránka s dalšími informacemi: http://groovy.dzone.com/
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
8 z 133
1.5 Dva způsoby zavedení třídy
Převzato z Groovy in Action, str. 48, Fig. 2.7
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
9 z 133
1.6 Přehled vlastnosti jazyka Groovy Bezešvá spolupráce s Javou:
Převzato z Groovy in Action, str 6, Fig. 1.2, str. 10, Fig 1.4 Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
10 z 133
1.7 Jak Groovy získat ► Groovy je součástí plné verze NetBeans; Eclipse a IDEA jej nabízí jako plug-in ► Instalace samostatné verze 1. Stáhnout poslední build z adresy http://groovy.codehaus.org/Download 2. Ve Windows stačí spustit instalační soubor, v ostatních prostředích je třeba: a) Rozbalit stažený ZIP do vybrané složky/adresáře b) Definovat systémovou proměnnou GROOVY_HOME a zadat do ní cestu k této složce c) Přidat do systémové proměnné PATH cestu ke složce %GROOVY_HOME%\bin (Windows)¨, resp. $GROOVY_HOME/bin (Unix + Linux) d) Otestovat správnost instalace zadáním příkazu groovy –v ► Instaluje se několik programů: ● groovy spouští skript zadaný v parametru či souboru ● groovysh spouští shell umožňující komunikovat prostřednictvím konzoly ● groovyConsole spouští "operativní" prostředí pro jednoduché programy a testy ● groovyc překladač do class-souborů ● groovydoc vytváří dokumentaci, ekvivalent programu javadoc ● … a některé další v závislosti na stažené verzi
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
11 z 133
1.8 Co Groovy od Javy přebírá ► Mechanismus balíčků a příkazy pro jejich definici (package) a import (import) ► Podobu příkazů a řídících struktur (podmíněné příkazy, cykly, …)
s výjimkou příkazu do { … } while()
► Definice tříd, výčtových typů, včetně interních tříd, avšak s výjimkou lokálních tříd ► Definice rozhraní včetně anotací ► Definice metod, operátory, výrazy, přiřazení ► Zpracování výjimek ► S drobnými úpravami deklarace literálů ● Největší rozdíl je v inicializaci polí, kde syntaxe Javy koliduje s jinými částmi Groovy a pole je proto třeba inicializovat prostřednictvím seznamů ● Na druhou stranu se doporučuje pole v programu nepoužívat, a dávat přednost seznamům ► Vytváření instancí, komunikace s objekty prostřednictvím odkazů, volání metod ► Syntaxi komentářů
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
12 z 133
1.9 Základní zjednodušení ► Groovy implicitně deklaruje importy: ● java.util.* ● groovy.lang.* ● groovy.util.*
● java.net.*
● java.lang.*
● java.io.*
● java.math.BigInteger ● java.math.BigDecimal
► V souboru může být několik veřejných tříd
a soubor se proto nemusí jmenovat podle obsažené veřejné třídy
► Na konci posledního příkazu na řádku není nutno psát středník ► Parametry volaných metod není nutno uzavírat do závorek ► Při zřetězeném volání metod není v řadě případů nutno používat tečky, např.
paint wall with red, green and yellow je ekvivalentní klasickému zápisu paint(wall).with(red, green).and(yellow)
► V metodách se nemusí psát závěrečný return,
automaticky se vrátí hodnota posledního výrazu/příkazu
► Všechny výjimky jsou převedeny na nekontrolované,
je-li zachytávaná výjimka typu Exception, nemusí se v catch uvádět její typ
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
13 z 133
► Deklarované atributy mají automaticky definované přístupové metody,
v Groovy je explicitně definujeme pouze tehdy, mají-li mít složitější tělo
► Zavádí literály i pro další datové typy – seznamy, mapy, intervaly ► Všechna data jsou objektových typů, takže i čísla mají metody ► Zavádí nové užitečné datové typy a programové konstrukce (zejména cykly) ► Nechceme-li v program deklarovat typ proměnné a nechat jeho odvození na překladači,
uvedeme deklaraci dané proměnné klíčovým slovem def
► Potřebujeme-li class-objekt, stačí napsat jméno třídy; není potřeba doplňovat .class ► V návěštích case příkazu switch lze použít téměř cokoliv ► Ve statických metodách zastupuje this odkaz na třídu, takže příkaz:
println("My classobject: " + this) vytiskne zadaný text následovaný podpisem class-objektu dané třídy ► Cokoliv lze testovat na „pravdivostní hodnotu“ (viz 1.9.2 Podmínky)
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
14 z 133
1.9.1 Skripty ► Ve skriptech nemusí být příkazy uzavřeny do metody,
a tím pádem ani do třídy – překladač vše doplní sám
current = 1; next = 1 //10.times { for (int i=0; i < 10; i++) { print current + ' ' newCurrent = next next = next + current current = newCurrent } println '' ► Ve skriptech nemusíme deklarovat použité proměnné –
stanou se automaticky lokálními proměnnými metody, kterou překladač definuje a uloží do ní veškerý kód nedefinovaný v nějaké metodě
► Podrobnosti o skriptech budou probrány v pasáži 3.2.2 Specifika skriptů
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
15 z 133
1.9.2 Podmínky ► V podmínkách jsou za „pravdivé“ jsou považovány výrazy, jejichž výsledkem je ● Booloean – true ● Character
– char != '\0'
● Collection
– size() > 0
● Enumeration
– hasMoreElements()
● Iterator
– hasNext()
● Map ● Matcher
– size() > 0 – Alespoň jeden vyhovující řetězec
● Number
– hodnota != 0
● Object[]
– length > 0
● Jiný objekt
– Odkaz != null
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
16 z 133
1.9.3 Sjednocení způsobů zjištění počtu prvků v kontejneru Type Array Array
Zjištění velikosti v JDK atribut length metoda java.lang.reflect.Array.getLength(array) String metoda length() StringBuffer metoda length() Collection metoda size() Map metoda size() File metoda length() Matcher metoda groupCount()
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
… a v Groovy size() method size() method size() size() size() size() size() size()
method method method method method method
17 z 133
1.10 Co dalšího je jinak ► Operátor == vždy volá metodu equals(Object).
Chceme-li porovnat shodnost odkazů, musíme použít metodu is(Object)
► Hodnoty s plovoucí čárkou jsou implicitně typy BigDecimal.
Chceme-li pracovat s double, musíme u literálu uvést příponu d nebo D
► Implicitní přístup ke třídám, metodám a vlastnostem je nyní public
ruší se implicitní přístup označovaný jako package private => zůstává pouze „svatá trojice“ public – protected – private
► Příkaz získání hodnoty atributu, resp. přiřazení hodnoty vlastnosti (atributu)
je ve skutečnosti voláním příslušné přístupové metody
► Groovy rozeznává více druhů textových řetězců – stringů; ty mohou být uvozeny: ● 'apostrofy' ● "uvozovkami" ● /lomítky/ ● $/Dolarovými lomítky/$ ● '''trojicí apostrovů''' ● """trojicí uvozovek""" ● Podrobnosti v pasáži Textové literály – stringy Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
18 z 133
1.11 Co Java má a Groovy (zatím) ne ► Cyklus do { ... } while(?) ► Přístupová práva package private ► Novinky syntaxe Javy 7 (v Groovy 2.0 již jsou0 již jsou0 již jsou metoda metoda) ● Diamantový operátor: List<string> list = new ArrayList<>()
_
● Kumulované catch definující reakci na několik výjimek současně ● Automatizované zavírání zdrojů
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
19 z 133
2.
Groovy – rychlý úvod do jazyka
Obsah 2.1 Aserce 2.2 Groovy Beans 2.3 Textové literály – stringy 2.3.1 Řetězce ohraničené apostrofy 2.3.2 Řetězce ohraničené uvozovkami – GStringy 2.3.3 Řetězce ohraničené lomítky 2.3.4 Řetězce ohraničené dolarovými lomítky 2.3.5 Možnosti změny hodnoty instancí typu GString 2.4 Žádný datový typ není primitivní 2.5 Seznam – List 2.6 Mapa – Map
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
2.7 Rozsah – Range 2.7.1 Definice 2.8 Uzávěr – Closure 2.9 Řídící algoritmické konstrukce 2.9.1 Rozšíření iterovatelných objektů 2.9.2 Interní iterátory zděděné od třídy Object 2.9.3 Metody aplikovatelné na čísla 2.9.4 Metoda with 2.10 Práce s čísly 2.11 Pojmenované parametry a jejich implicitní hodnoty
20 z 133
2.1 Aserce ► Programy v dynamických jazycích se musí mnohem podrobněji testovat ► V Groovy se hojně využívá konstrukce assert,
přičemž se využívá obecněji pojaté podmínky
assert true assert (x = 1) //Přiřazení ‐ výsledek je přiřazovaná hodnota assert (x == 1) //Logický výraz assert x //Aritmetická hodnota assert "Kuk" //Odkaz na objekt ► V Javě je assert klíčové slovo definující příkaz,
pro nějž můžeme v parametrech virtuálního stroje nastavit, jestli se pro danou část programu má či nemá spouštět
► V Groovy není assert klíčové slov, ale metoda, která se volá pokaždé ► Groovy podporuje dvouparametrický assert,
v němž druhý parametr specifikuje chybu; na rozdíl od Javy však není druhý parametr oddělen dvojtečkou, ale čárkou
assert false, "Zde je uměle vytvořená chyba" Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
21 z 133
► Výrazy vyhazující výjimku
//assert false //Logická hodnota //assert 0 //Číslo 0 //assert "" //Prázdný řetězce //assert null //Prázdný odkaz ► Výše uvedené hodnoty lze otestovat prostřednictvím jejich negace
assert ! null; assert ! 0; assert ! "" ► Vznikne-li při vyhodnocování argumentu funkce assert chyba,
program se nám pokusí nejen vysvětlit, kde při vyhodnocování výrazu našel chybu, ale poskytne nám i informace k následnému odhalení její příčiny
Exception in thread "main" Assertion failed: assert b == (a+a) | | ||| 9 | 5|5 | 10 false
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
22 z 133
2.2 Groovy Beans ► Koncept JavaBeans zavedl jisté konvence
umožňující komponentový přístup k objektům
► Groovy používá stejné konvence jako Java,
avšak zodpovědnost za definici přístupových metod bere na svá bedra překladač
class Book { String title } def groovyBook = new Book() ► Předchozí program Groovy přeloží tak, že: ● Definuje soukromý atribut title ● Definuje veřejnou metodu getTitle(), která vrátí jeho hodnotu ● Definuje veřejnou metodu setTitle(String), která nastaví jeho hodnotu ► K atributu pak můžeme přistupovat zdánlivě přímo, nicméně toto přímé oslovení
překladač nahradí odpovídajícím voláním přístupové metody
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
23 z 133
► Přímé volání metod
def Book book = new Book() book.setTitle("Groovy conquers the world") assert book.getTitle() == "Groovy conquers the world" ► Nepřímé volání metod – přístup prostřednictvím metod zabezpečí překladač
(jinými slovy: výsledný kód je stejný jako v předchozí ukázce)
book.title = "Groovy in Action" assert book.title == "Groovy in Action" book.title += '!' //Překlad: book.setTitle(book.getTitle() + '!') assert book.getTitle() == "Groovy in Action!"
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
24 z 133
2.3 Textové literály – stringy ► Řetězec ohraničený jedním apostrofem – 'řetězec' ► Řetězec ohraničený třemi apostrofy – '''řetězec''' ► Řetězec ohraničený jedněmi uvozovkami – "řetězec" ► Řetězec ohraničený třemi uvozovkami – """řetězec""" ► Řetězec ohraničený lomítky – /řetězec/ ► Řetězec ohraničený dvojící lomítko + znak $ – $/řetězec/$
Každý z těchto řetězců má vlastní sémantiku
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
25 z 133
2.3.1 Řetězce ohraničené apostrofy ► Klasický řetězec Javy pouze vyměnil ohraničující uvozovky za apostrofy
a1 = 'Klasický řetězec' ► Řetězec uvozený třemi apostrofy je klasický řetězec, který však může zabírat několik
řádků, přičemž konce řádků zadané v kódu se respektují
● Při používání víceřádkových řetězců se případné odsazení dalšího řádku, stane součástí výsledného řetězce => proto musí další řádek začínat zkraje
a3 = '''Řetězec uvozený třemi apostrofy může zabírat několik řádků, přičemž konce řádků zadané v kódu se respektují''' ► Stačí-li nám rozdělit řetězec na řádky pouze ve zdrojovém kódu,
můžeme jako poslední znak na řádku použít zpětné lomítko; toto odřádkování se však v zadávaném řetězci neobjeví
ab = 'Zpětné lomítko na konci řádku \ nevloží odřádkování do výsledného textu' assert ab == 'Zpětné lomítko na konci řádku nevloží odřádkování do výsledného textu' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
26 z 133
2.3.2 Řetězce ohraničené uvozovkami – GStringy ► Řetězec ohraničený uvozovkami je tzv. GString – ten může obsahovat výrazy,
které se píší za znak $ a vyhodnocují se v době použití řetězce
● Je-li výraz složitější než pouhý název proměnné, vkládá se do složených závorek { } ● Výraz v závorkách může obsahovat i volání metod
pr = 'proměnnou'; slo='složitý'; vý='VÝRAZ' u1 = "GString může vyhodnocovat $pr či celý ${slo + " " + vý.toLowerCase()}" ► I uvozovkami ohraničené řetězce mají svojí „trojitou“ verzi
umožňující zapsat řetězec obsahující několik řádků
u3 = """Odvoláváme‐li se pouze na $pr, nemusíme její název psát do závorek, Ty využijeme až když chceme vyhodnotit ${slo + " " + vý.toLowerCase()}""" ► Zpětné lomítko na konci řádku máme k dispozici i v řetězcích ohraničených
uvozovkami; ani zde se v zadávaném řetězci odřádkování neobjeví
ub = " Zpětné lomítko na konci řádku, \ se ve výsledku neprojeví" assert ub == "Zpětné lomítko na konci řádku, se ve výsledku neprojeví" Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
27 z 133
2.3.3 Řetězce ohraničené lomítky ► Zpětné lomítko se v nich nemusí zadávat jako dvojice zpětných lomítek
(je to zde běžný znak), a proto se používají především k zadávání regulárních výrazů
► Lomítko se v nich zadá pomocí escape sekvence \/ ► Kromě lomítka znají ještě escape sekvence \uHHHH ► Mohou obsahovat „dolarové výrazy“ ► Od verze 1.8 jsou více řádkové, tj. ve vztahu ke konci řádku ve zdrojovém kódu
se chovají jako „trojité“ verze předchozích řetězců
lo = /Zpětná lomítka (\) zde nemusíme zdvojovat. Tyto řetězce považují zpětné lomítko za prefix pouze tehdy, je‐li následované dopřeným lomítkem (\/ ‐ pak je dvojice chápána jako lomítko), anebo znakem 'u' ‐ např. znak \u00A9 je vyhodnocen jako ©./ l$ = /I zde lze použít výraz s $pr či celý ${slo + " " + vý}/
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
28 z 133
2.3.4 Řetězce ohraničené dolarovými lomítky ► Tyto řetězcové literály zavedlo Groovy 1.8 ► Ohraničíme-li „lomítkový řetězec“ navíc znaky $ (dolar),
změníme tím standardní „escapovací“ znak \ na znak $ ● Znak $ bude uvozovat lomítko či sám sebe ● Znak \ bude nyní uvozovat pouze sekvenci \uHHHH
$l = $/Znak dolar nyní píšme $$ a lomítko $/ avšak znak \u00A9 je nadále vyhodnocován jako © /$
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
29 z 133
2.3.5 Možnosti změny hodnoty instancí typu GString ► Apostrofové řetězce jsou instancemi třídy java.lang.String ► Uvozovkové řetězce obsahující „dolarové výrazy“ jsou instancemi třídy
org.codehaus.groovy.runtime.GStringImpl, po vyhodnocení výrazů se vytvářejí instance třídy String ► Při používání GString-ů je třeba mít na paměti, že parametry se předávají hodnotou,
takže má-li se hodnota parametru měnit v průběhu výpočtu, musí být parametr instancí nějakého proměnného typu
► Druhou možností je předat bezparametrický uzávěr – např. ${‐> string} ● Podrobnosti o uzávěrech viz pasáž 2.8 Uzávěr – Closure ► Příklad k předchozímu tvrzení uvedený na další stránce demonstruje: ● Objekty typu String jsou neměnné, takže jakákoliv změna vede k vytvoření nového objektu ● Objekty typu StringBuilder jsou proměnné, takže GStringy, které je používají, mohou v průběhu času měnit svoji hodnotu ● Objekty typu String se dají použít jako proměnně, jsou-li „správně uzavřené v dolarových závorkách“
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
30 z 133
str = 'oves' ;assert str instanceof String buf = new StringBuffer('oves') ;assert buf instanceof StringBuffer us = "Skákal pes přes $str" ub = "Skákal pes přes $buf" uc = "Skákal pes přes ${‐>str}" assert us == 'Skákal pes přes oves' assert ub == 'Skákal pes přes oves' assert uc == 'Skákal pes přes oves' str = 'zelenou louku' buf.replace(0, 5, 'zelenou louku') assert us == 'Skákal pes přes oves' assert ub == 'Skákal pes přes zelenou louku' assert uc == 'Skákal pes přes zelenou louku'
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
31 z 133
► Při volání metody se situace zjednodušuje,
protože při každém volání se řetězec vyhodnocuje znovu, takže není třeba zabezpečovat změnu
def method(param) { return "Parametr: $param" } assert method(1) == 'Parametr: 1' assert method('Groovy') == 'Parametr: Groovy'
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
32 z 133
2.4 Žádný datový typ není primitivní ► Všechny hodnoty primitivních typů se chovají jako objekty,
takže jim lze posílat zprávy = volat jejich metody
def s = '' 3.times { s += 'bu' } assert s == 'bububu' def x = 1 assert x instanceof Integer ► Operátory jsou ve skutečnosti definovány jako metody =>
použití operátoru nebo zavolání odpovídající metody má proto stejný efekt
def x = 1 def y = 2 assert x + y == 3 assert x.plus(y) == 3 assert (x ‐ y) * y == ‐2 assert x.minus(y).multiply(y) == ‐1
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
33 z 133
2.5 Seznam – List ► Skupina čárkami oddělených hodnot v hranatých závorkách je chápána
jako literál seznamu => seznam tak můžeme inicializovat přímo v deklaraci
► K prvkům seznamů můžeme přistupovat prostřednictvím indexů obdobně,
jako přistupujeme k prvkům pole
def roman = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII'] assert roman[4] == 'IV' //Přístup k prvku seznamu roman[8] = 'VIII' //Přidání prvku do seznamu assert roman.size() == 9 ► Seznam se automaticky konvertuje na objekt libovolného typu tak,
že překladač zavolá konstruktor, jemuž se prvky seznamu předají jako parametry (třída samozřejmě musí daný konstruktor mít)
java.awt.Point point = [10, 20] // Ekvivalentní new Point(10, 20) assert point.toString() == 'java.awt.Point[x=10,y=20]' list = [100, 200] point = list assert point.toString() == 'java.awt.Point[x=100,y=200]' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
34 z 133
► Hodnoty seznamu lze přiřadit členům skupiny proměnných uzavřených v závorkách
list = [10, 'xxx', true] (a, b, c) = list assert (a==10) && (b=='xxx') && c (c, b, a) = ['third', 'second', 'first'] assert (a=='first') && (b=='second') && (c=='third') ► Přebytečné prvky v seznamu se zahodí, chybějící se přiřadí jako null
(k, l, m) = [1,2,3,4] assert (k==1) && (l==2) && (m==3) (x, y, z) = [10,20] assert (x==10) && (y==20) && (z==null)
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
35 z 133
2.6 Mapa – Map ► Skupina čárkami oddělených dvojic hodnot, které jsou vzájemně odděleny dvojtečkou,
je chápána jako literál mapy => i mapy lze inicializovat přímo v deklaraci ● V této definici je objekt vlevo od dvojtečky chápán jako klíč a objekt vpravo od dvojtečky jako hodnota
► Mapu můžeme inicializovat přímo v deklaraci jako seznam dvojic klíč : hodnota
def http = [ //Deklarace inicializované mapy 100 : 'CONTINUE', 200 : 'OK', 400 : 'BAD REQUEST' ] assert http[200] == 'OK' //Prvek indexujeme hodnotou klíče http[200] = 'YES' //Změna hodnoty existujícího prvku http[500] = 'INTERNAL SERVER ERROR' //Přidání prvku do mapy assert http.size() == 4
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
36 z 133
► Mapa se automaticky převede na jiný typ tak,
že překladač zavolá jeho bezparametrický konstruktor a poté přiřadí hodnoty prvků mapy stejně pojmenovaným atributům ● Cílový typ musí mít bezparametrický konstruktor, nastavované atributy mohou být soukromé, ale nesmějí být konstantní,
java.awt.Point point = [y:20, x:10] // Ekvivalentní new Point(10, 20) assert point.toString() == 'java.awt.Point[x=10,y=20]' map = [x:100, y:200] point = map assert point.toString() == 'java.awt.Point[x=100,y=200]' class MappedClass { private int x, y, z; String toString() { return "MappedClass[x=$x, y=$y, z=$z]" } } MappedClass mc = [y:200, z:300] //Hodnota x se nezmění assert mc.toString() == 'MappedClass[x=0, y=200, z=300]' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
37 z 133
2.7 Rozsah – Range ► Rozsahy nejsou ve standardní knihovně Javy, Groovy je přidává ► Literálem rozsahu je dvojice porovnatelných hodnot oddělených dvěma tečkami;
tyto hodnoty budeme označovat jako dolní a horní mez
► Je-li před druhou hodnotou menšítko, nepočítá se horní mez do rozsahu
def x = 1..10; def y = 1..<10 assert x.contains(10); assert y.contains(9) assert x.contains(15) == false; assert y.contains(10) == false assert x.size() == 10; assert y.size() == 9 assert x.from == 1; assert y.from == 1 assert x.to == 10; assert y.to == 9 assert x.reverse() == 10..1; assert y.reverse() == 9..1 ► Rozsahy lze s jistou rezervou považovat za seznamy s posloupností hodnot
def numberList = [1,2,3,4,5,6,7,8,9,10] def numberRange = 1..10 assert numberList == numberRange //Jsou ekvivalentní, ale každý jiného typu assert (numberList.class == ArrayList) && (numberRange instanceof Range) Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
38 z 133
2.7.1 Definice ► Rozsahy mohou být definovány nad libovolným „uspořádaným typem“,
tj. typem, který:
● Definuje metody next() and previous(), resp. operátory ++ a ‐‐ vracející následníka, resp. předchůdce dané instance ● Implementuje java.lang.Comparable, takže definuje metodu compareTo, resp. ekvivalentní operátor <=> (spaceship operator) ► Přítomnost prvku v rozsahu zjišťujeme operátorem in:
if (objekt in rozsah) ► Tentýž operátor lze použít v cyklu for místo dvojtečky
enum DAY { Sun, Mon, Tue, Wed, Thu, Fri, Sat } working = DAY.Mon..DAY.Fri report = '' for (day in working) { report += day.toString() + ' ' } assert report == 'Mon Tue Wed Thu Fri ' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
39 z 133
2.8 Uzávěr – Closure ► Myšlenka, že funkce (a obecně část kódu) je běžným objektem jazyka
stejně jako např. číslo, byla implementována již v roce 1958 v jazyce LISP ● Teoretickým základem je tzv. λ-kalkulus zavedený Churchem v roce 1936
► V klasických procedurálních jazycích (Fortran – Algol – Pascal – …)
jsou procedury a funkce definovány jako zvláštní entity, které není možné uložit do proměnných
► Moderní programovací jazyky se snaží znovu začít pracovat s částmi kódu
jako s entitami postavenými na roveň datům
► Termín uzávěr (closure) označuje skutečnost, že takováto část kódu
musí nějakým způsobem uzavřít proměnné, s nimiž bude pracovat, aby nebyly v nevhodnou chvíli systémem odstraněny
► V OO jazycích nepoužívajících uzávěry se funkčnost uzávěrů
nahrazuje použitím návrhového vzoru Příkaz (Command), který zabalí do objektu příslušnou část kódu jako metodu daného objektu ● Příklad: java.io.File.list(FilenameFilter) ● Nevýhodou tohoto přístupu je zbytečný nárůst počtu explicitně definovaných datových typů (třída + rozhraní) a výrazně „zašuměný“ zdrojový kód
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
40 z 133
► V příkladu na obrázku je volána metoda
each(groovy.lang.Closure) seznamu, což je metoda, o níž Groovy rozšiřuje portfolio metod třídy Object.
► Tato metoda vyžaduje jako svůj parametr
uzávěr, který definuje, co se má udělat s každým z prvků daného iterovaného objektu
Převzato z Groovy in Action, str 44, Fig. 1.2
► Zobrazené volání využívá pravidla, že má-li metoda jako svůj poslední parametr uzávěr,
nemusí jej uvádět v seznamu parametrů, ale může jej uvést až za ním
► V tomto případě byl uzávěr jediným parametrem,
takže seznam klasických parametrů zcela chybí
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
41 z 133
► Uzávěry mají své parametry vyjmenované za otevírací složenou závorkou
a oddělené od vykonávaného kódu šipkou ‐>
► Je-li parametr jen jeden, nemusí se uvádět a použije se pro něj implicitní název it
list1 = [1, 3, 5, 7] list2 = list1.collect{ it *= 2 } //Vyrobí ze zpracovaných hodnot seznam assert list2 == [2, 6, 10, 14] ► Není-li parametr žádný, napíše se samotná šipka: { ‐> kódUzávěru } ● Takovýto uzávěr byl použit např. v ukázce vyhodnocování GString-ů ► Podrobněji se k uzávěrům vrátíme v samostatné kapitole Uzávěry
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
42 z 133
2.9 Řídící algoritmické konstrukce ► Konstrukce if, while, switch a klasický cyklus
for(inicializace, podmínka, modifikace) fungují jako v Javě
def list = [] for (int j=0; j < 5; j++) { list += j } assert list == [0, 1, 2, 3, 4] ► Cyklus do { … } while(…) nelze použít, Groovy nezná klíčové slovo do ► „Dvojtečkový“ cyklus for (cyklus for each) nahrazuje dvojtečku slovem in
string = '' for (num in 0..9) { string += num } assert string == '0123456789'
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
43 z 133
2.9.1 Rozšíření iterovatelných objektů ► Groovy výrazně rozšiřuje paletu iterovatelných objektů;
iteruje se podle následujících pravidel (a priorit): 1. java.util.Iterator................ Použije jej 2. org.w3c.dom.NodeList ............ Iterace přes uzly
3. java.util.Enumeration .......... Převede jej na Iterator 4. java.util.regex.Matcher ...... Iterace přes výskyty 5. Metoda iterator .................... Zavolat 6. Collections ............................... Collection.iterator 7. java.util.Map ......................... Iterace přes objekty Map.Entry 8. Array ......................................... Iterace přes prvky 9. MethodClosure ......................... Iterace přes volání 10. java.lang.String ................... Iterace přes znaky 11. java.io.File ........................... Iterace přes řádky 12. null ........................................... Prázdný iterátor 13. Ostatní ...................................... Použije se pouze daný objekt
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
44 z 133
2.9.2 Interní iterátory zděděné od třídy Object ► Kromě klasických řídících konstrukci Groovy zavádí ve třídy Object řadu metod,
které pracují jako interní (dávkové) iterátory a které je možno využívat jako náhradu složitěji programovaných cyklů
► Třída Object definuje následující „interní iterátory“ (v následujícím výčtu
jsou uzávěry uváděny rovnou až za případným seznamem zbylých parametrů): ● Boolean any {closure} Aplikuje postupně uzávěr na jednotlivé hodnoty (dále jen APUJH) a zjišťuje, vrátí-li uzávěr pro některou z hodnot true (přesněji zobecněné true, tj. hodnotu, jíž Groovy považuje za true – obdobně i dále) ● List collect {closure} APUJH a z vrácených hodnot vytvoří seznam
list1 = [1, 3, 5, 7] list2 = list1.collect{ it *= it } //Vyrobí ze zpracovaných hodnot seznam assert list2 == [1, 4, 25, 49]
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
45 z 133
● Collection collect(Collection collection) {closure} APUJH a vracené hodnoty uloží do zadané kolekce
set1 = [1,2,3] as HashSet set2 = [2,4,5,6].collect(new HashSet()) { (int)(it / 2) } assert set1 == set2 ● (void) each {closure} APUJH
list = [0, 1, 2, 3, 4] string = '' list.each() { item ‐> string += (2*item) } assert string == '02468' ● (void) eachWithIndex {closure} APUJH, přičemž uzávěru vždy předá vedle hodnoty i její index počínaje nulou ● Boolean every {closure} APUJH a zjišťuje, zda pro všechny vrátí true ● Object find {closure} APUJH a vrátí první hodnotu, pro kterou uzávěr vrátí true ● List findAll {closure} APUJH a vrátí seznam všech hodnot, pro něž uzávěr vrátí true ● Integer findIndexOf {closure} APUJH a vrátí index první hodnoty, pro kterou uzávěr vrátí true Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
46 z 133
● int findIndexOf(int startIndex) {closure} Jako minulá, ale začne prohledávat od prvku s daným indexem ● List findIndexValues(Closure closure) APUJH a vrátí seznam indexů prvků, pro něž uzávěr vrátí true ● List findIndexValues(Number startIndex, Closure closure) Jako minulá, ale začne prohledávat od prvku s daným indexem ● int findLastIndexOf(Closure closure) APUJH a vrátí index poslední hodnoty, pro kterou uzávěr vrátí true ● int findLastIndexOf(int startIndex, Closure closure) Jako minulá, ale začne prohledávat od prvku s daným indexem ● Object findResult(Object defaultResult, Closure closure) APUJH a vrátí první nenullový výsledek. Pokud na žádný nenarazí, vrátí defaultResult ● Object findResult(Closure closure) Jako minulá s defaultResult == null ● List grep(Object filter) APUJH a vrátí seznam hodnot vyhovujících zadanému filtru
text = 'Příliš žluťoučký kůň úpěl ďábelské ódy' gt128 = text.grep { it > 128 } assert gt128 == ['ř', 'í', 'š', 'ž', 'ť', 'č', 'ý', 'ů', 'ň', 'ú', 'ě', 'ď', 'á', 'é', 'ó']
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
47 z 133
● Object inject(Object initialValue, Closure closure) APUJH přičemž si jednotlivá volání předávají v parametru proměnnou, jejíž počáteční hodnota je v parametru a výslednou hodnotu metoda vrátí
gt128 = gt128.inject('') { str, ch ‐> str += ch } assert gt128 == "říšžťčýůňúěďáéó" ► Testovací metody any, every, find, findAll a grep
mají i verze bez uzávěrového parametru – tyto verze pak nevyhodnocují aplikaci uzávěru, ale jen zda hodnoty lze považovat za true
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
48 z 133
2.9.3 Metody aplikovatelné na čísla ► I čísla jsou objekty; nabízejí metody použitelné pro realizaci cyklů ► Pro celá čísla lze použít metody realizující cykly – např. times
def stars = '' 5.times {stars += it + '*'} assert stars == '0*1*2*3*4*' ► … nebo metodu upTo
def list2 = [] 0.upto(4) { list2 << it } assert list2 == list ► … případně metodu downTo
4.downto(0) { list2 += it } assert list2 == list + [4, 3, 2, 1, 0] Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
49 z 133
► … anebo metodu step realizující cyklus se zadaným krokem
store = '' 0.step(0.5, 0.1 ) { // Od 0 do 0,5 po 0,1 number ‐> store += number + ' ‐ ' } assert store == '0 ‐ 0.1 ‐ 0.2 ‐ 0.3 ‐ 0.4 ‐ '
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
50 z 133
2.9.4 Metoda with ► Groovy zdánlivě zavádí konstrukci with ● Je to ve skutečnosti metoda třídy Object, jejímž parametrem je uzávěr
java.awt.Rectangle rect = new java.awt.Rectangle() rect.with { x = 1 y = 2 width = 3 height = 4 } assert rect == new java.awt.Rectangle(1, 2, 3, 4)
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
51 z 133
2.10 Práce s čísly assert 1 == (‐1).abs() assert 2 == (int) 2.5 //Standardní přetypování assert 2 == 2.5.toInteger() //Konverze assert 2 == 2.5 as Integer //Univerzálně dostupný operátor konverze // ‐ volá se netoda asType(Class) assert 3 == 2.5f.round() //Zaokrouhlení na celé číslo assert 3.1416 == Math.PI.round(4) //Zaokrouhlení na 4 desetinná místa assert 4 == 4.5f.trunc() //Odříznutí desetinné části assert 27.18 == (10*Math.E).trunc(2) //Zaokrouhlení na 2 desetinná místa assert '6 times' == 6 + ' times' //Číslo + String assert '2.718'.isNumber() //Metoda třídy String assert 5 == '5'.toInteger() //Metoda třídy String assert 5 == '5' as Integer //Metoda třídy String zděděná z Object assert 53 == (int) '5' //Jednoznakový string převede na char, //... takže výraz vrátí kód znaku ► Nepřevádějte stringy na čísla přetypováním;
String se převede na znak a jeho kód se pak použije jako převedená hodnota => používejte konverze
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
52 z 133
2.11 Pojmenované parametry a jejich implicitní hodnoty ► Groovy umožňuje přiřadit parametrům metod implicitní hodnoty,‘
které se dosadí v případě, že parametr nebude zadán
def static sumWithDefaults(a=1, b=2, c=3) { return a + b + c } assert 6 == sumWithDefaults() assert 15 == sumWithDefaults(10) assert 33 == sumWithDefaults(10, 20) assert 60 == sumWithDefaults(10, 20, 30) ► Při zadávání implicitních hodnot je výhodné zadávat implicitní hodnoty odzadu;
protože při vyhodnocování parametrů Groovy:
● spočte, kolik parametrů má k dispozici, ● doplní chybějící počet hodnot zadanými implicitními hodnotami, přičemž parametry, jimž bude přiřazena zadaná implicitní hodnota, bere odpředu ● přiřadí hodnoty parametrům a metodu zavolá.
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
53 z 133
► Groovy umožňuje vyvolat iluzi, že používáte při volání metod názvy jejich parametrů;
protože však Java neumožňuje uchovávat názvy parametrů, dosahuje se tohoto zdání definicí metody s parametrem typu Map
● Příkazy tisku v následujícím programu mají ozřejmit mechanismus přiřazování hodnot
def static sumNamed(Map args) { if (args== null) args = [:] int i = 1 ['a','b','c'].each{ //Nebude‐li v mapě existovat dvojice se args.get(it, (i *= 10)) //zadaným klíčem, dosadí se mocnina deseti println "it=" + it + ", i=" + i + ", args=" + args } def result = args.a + args.b + args.c println "reslut=" + result println() return result } assert 1110 == sumNamed() assert 1103 == sumNamed(a:3) assert 1005 == sumNamed(a:3, b:2) assert 6 == sumNamed(c:1, b:2, a:3) assert 111 == sumNamed(c:1) Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
54 z 133
3.
Hlubší pohled na práci s daty
Obsah 3.1 Druhy typování a jazyků 3.1.1 Statické typování 3.1.2 Dynamické typování 3.1.3 Kachní typování (duck typing) 3.1.4 Srovnání a přístup Groovy 3.1.5 Silné × slabé typování 3.1.6 Dynamická podstata Groovy 3.2 Podobnosti a rozdíly deklarace typů 3.2.1 Atributy a lokální proměnné ve standardních třídách 3.2.2 Specifika skriptů 3.3 Přetěžování operátorů 3.3.1 Nové operátory 3.4 Textové řetězce a jejich metody 3.4.1 Automatizované využívání třídy StringBuffer 3.5 Regulární výrazy 3.5.1 Základní operátory 3.5.2 Realizace vyhledávání, shody a nahrazování Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
3.5.3 3.6
3.7
3.8 3.9
Práce s jednotlivými výskyty vzoru v řetězci 3.5.4 Regulární výrazy v příkazech switch Pracujeme se seznamy 3.6.1 Práce s operátorem indexace 3.6.2 Operátory přidávání a odstraňování položek 3.6.3 Seznamy v řídících konstrukcích 3.6.4 Metody manipulující s obsahem 3.6.5 Aplikace kódu na prvky seznamu 3.6.6 Dotazování a vyhledávání 3.6.7 Příklad: Definice algoritmu QuickSort Pracujeme s mapami 3.7.1 Přístup k položkám mapy 3.7.2 Informace o mapě 3.7.3 Aplikace kódu na položky v mapě 3.7.4 Práce s obsahem Možnosti příkazu switch Metoda with(Closure) 55 z 133
3.1 Druhy typování a jazyků ► Statické × dynamické typování
se liší podle toho, kdy se vyhodnocují informace o datovém typu
► Staticky typované jazyky deklarují
typ hodnot uložitelných do proměnné a překladač trvá na jejich dodržení
► Silné (bezpečné) × slabé typování
se liší dle toho, zda může entita (objekt) měnit svůj datový typ za běhu programu
► Nezaměňovat dynamičnost typování s
dynamičností jazyka – existují i staticky typované dynamické jazyky (např. Scala)
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
Převzato z Programming Groovy, str 80, Fig. 4.1
56 z 133
3.1.1 Statické typování ► Při statickém typování musí být typ použitého objektu znám již při překladu =>
typy je třeba definovat již ve zdrojovém kódu
► Kontrola typů použitých objektů při překladu
je formou (částečné) verifikace programu
► Znalost typů objektů umožňuje překladači vytvořit efektivnější kód ► Umožňuje odhalit potenciální chyby i v řídce používaných částech programu;
bez statického typování nezaručí odhalení některých chyb ani 100% pokrytí testy
► Statické typování umožňuje vývojovému prostředí
nabídnout uživateli výrazně bohatší podporu, zejména pak možnosti refaktorace a automatického doplňování kódu
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
57 z 133
3.1.2 Dynamické typování ► Při dynamickém typování se typ použitého objektu zjišťuje až za běhu ► Při tomto typování mají své typy hodnoty, avšak ne proměnné ► Typ hodnoty v proměnné se zjišťuje, až když se s ní má pracovat ► Dynamické typování umožňuje snadnou realizovatelnost některých algoritmů,
které bychom při statickém typování definovali velice pracně
► Dynamicky typovaný kód nic nezaručuje,
a proto může při jeho provádění dojít k chybám, které by překladač při statickém typování bezpečně vyloučil
► => Při dynamickém typování je třeba mnohem pečlivější provádění testů
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
58 z 133
3.1.3 Kachní typování (duck typing) ► Zvláštní druh dynamického typování, který nehledí na deklarovaný typ objektu ► Název vznikl podle tzv. kachního testu, který definoval básník James Whitcomb Riley:
“When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck.”
► Při kachní typování se nehledí na skutečný typ objektu,
ale vyhodnocuje se pouze to, je-li schopen reagovat na zaslanou zprávu
► Kachní typování tak umožňuje polymorfismus bez dědičnosti ► Kachní typování najdeme většinou v dynamicky typovaných jazycích;
některé staticky typované jazyky (např. C# 4.0) umožňují označit parametr jako dynamicky vyhodnocovaný – „kachní“
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
59 z 133
3.1.4 Srovnání a přístup Groovy ► Po počátečním boomu dynamicky typovaných jazyků jejich obliba upadá ● Server Tiobe sledující popularitu jazyků uvádí pro rok 2008 špičku 44%, v srpnu 2011 hodnota klesla o čtvrtinu na 34 % a trend je stále klesající ► Uživatelům na dynamicky typovaných jazycích vadí: ● Neschopnosti vývojových prostředí poskytnout pro tyto jazyky stejně komfortní podporu jako pro jazyka staticky typované ● Čas ušetřený volnějšími typovacími pravidly bývá často vykoupen časem potřebným pro psaní podrobnějších testů a hledání některých chyb ► Groovy umožňuje jak statické, tak dynamické, tak kachní typování
podle okamžité výhodnosti
● Zadáme-li v deklaraci proměnné její typ tak, jako bychom to udělali v Javě, bude vnímána jako staticky typovaná ● Zadáme-li v deklaraci proměnné místo jejího typu klíčové slovo def, případně pokud ve skriptu (jinde to nejde) danou proměnnou vůbec nedeklarujeme, bude vnímána jako dynamicky typovaná ● U dynamicky typovaných proměnných můžeme oslovovat jejich členy (atributy, metody, interní typy) aniž bychom věděli, že je daná třída má; jejich existence se zjišťuje až za běhu v okamžiku oslovení daného členu. Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
60 z 133
3.1.5 Silné × slabé typování ► Silně typovaný (typově bezpečný) jazyk neumožňuje,
aby entita za běhu změnila svůj typ, přičemž za změnu typu se nepočítá, když se:
● objekt jednoho typu začne (vratně) vydávat za instanci některého ze svých předků, resp. implementovaných rozhraní ● objekt vydávající se za instanci předka opět začne přiznávat svůj původní typ ► Za změnu datového typu se naopak počítá, když je provedená změna nevratná,
anebo je cílový typ mimo rodičovskou hierarchii původního typu ● U C++ k tomu může dojít i při přetypování na předka
► Při silném typování se nemění interpretace obsahu paměti zabrané objektem,
při slabém typování se tato interpretace změnit může
● Např. následující program v C/C++ „zařídí“, aby se k dané proměnné mohlo současně přistupovat jak celému i reálnému číslu: double d = 1.5; long* pl = (long*) ((void*)(& d)) ► Groovy je ze své podstaty silně typovaný jazyk
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
61 z 133
3.1.6 Dynamická podstata Groovy ► Vzhledem k dynamické podstatě jazyka se může stát,
že dva textově naprosto shodné programy poběží různě v závislosti na tom, který překladač na daný text aplikujeme (viz ukázka na následující straně)
► Následující program demonstruje jeden z (menších) rozdílů mezi
statickou podstatou jazyka Java a dynamickou podstatou jazyka Groovy – při každém z těchto přístupů bude z metody main zavolání jiná přetížená verze metody mtd ● Při použití jazyka Java se zavolá přetížená verze metody, jejíž typ parametru odpovídá deklarovanému typu – Object ● Při použití jazyka Groovy se zavolá přetížená verze metody, jejíž typ parametru odpovídá skutečnému typu – String
► Program můžete uložit jednou jako soubor s příponou .java
a podruhé jako soubor s příponou .groovy.
● V každém souboru na konci odkomentujete řádek s komentářem označujícím daný jazyk – pak program projde bez chyby. ● Odkomentujete-li řádek pro jiný jazyk, metoda assert vyvolá výjimku
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
62 z 133
public class P03_01_06_01_Java_Groovy { public static String mtd(Object o) { return "Object: " + o.toString(); } public static String mtd(String o) { return "String: " + o.toString(); } public static void main(String[] args) { String s = "Já jsem string"; Object o = s; assert mtd(s).equals("String: Já jsem string"); // assert mtd(o).equals("Object: Já jsem string"); //Java // assert mtd(o).equals("String: Já jsem string"); //Groovy } }
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
63 z 133
3.2 Podobnosti a rozdíly deklarace typů ► V Groovy můžeme použít modifikátory známé z Javy ● private, protected a public (přístup package private Groovy nezavádí), implicitní je public ● final pro zákaz změny hodnoty ● static pro označení příslušnosti k třídě ► Definice typu je nepovinná ● Je-li typ definován, musí se dodržet jako v Javě ● Není-li typ definován, zjišťuje se dynamicky za běhu ► Při deklaraci proměnné či metody musíme před názvem deklarovaného objektu
vždy uvést nějaký modifikátor nebo datový typ nebo def
● Výjimkou z tohoto pravidla tvoří skripty, v nichž můžeme použít proměnnou bez deklarace, protože si jí skript deklaruje automaticky sám ► Neuvedením datového typu požadujeme jeho dynamické vyhodnocování za běhu ► Není-li u atributu uvedena jeho viditelnost,
je vytvořena veřejná vlastnost s názvem tohoto atributu
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
64 z 133
3.2.1 Atributy a lokální proměnné ve standardních třídách ► V programu musí být všechny atributy a lokální proměnné deklarovány;
bez deklarace mohou proměnné zůstat pouze ve skriptu, kde překladač zajistí implicitní deklaraci
► Následující definice třídy je rozdělena do dvou částí: ● Deklarace atributů
class P03_02_01_01_Declarations { String typedField def untypedField public untypedFieldWithModifier private assignedField = new Date() final readOnlyField = "RO" protected field1, field2, field3 //Následující příkaz je zakomentován, protož způsobí syntaktickou chybu // notDeclared = "Syntax Error" static untypedClassField public static final String CONSTA = 'a', CONSTB = 'b' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
65 z 133
● Definice metody
def mtdCls() { def localUntypedMethodVar = 1 int localTypedMethodVar = 1 def localVarWithoutAssignment, andAnotherOne final localConstant = 0 try { //Překladač očekává, že použitou proměnnou najde za běhu localWithoutModifier = "RuntimeError" assert false } catch(Exception e) { //Nenašel assert true } } } ● Ověření
def cls = new P03_02_01_01_Declarations() cls.mtdCls()
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
66 z 133
3.2.2 Specifika skriptů ► Požijeme-li v souboru jakýkoliv příkaz mimo tělo metody,
je daný soubor automaticky považován za skript
● Je definována třída, která je potomkem třídy groovy.lang.Script a je pojmenovaná stejně jako daný soubor ● V souboru skriptu proto nesmí být definována třída pojmenovaná stejně jako daný soubor ● V této třídě je definována metoda public Object run(), do níž se umístí všechny příkazy vně metod a inicializačních bloků, a spouštěcí metoda public static transient void main(String args[]), která metodu run zavolá, takže daný skript (resp. jeho class-soubor) je možno spustit jako aplikaci Javy ► Ve skriptu jsou nedeklarované a rovnou použité proměnné
považovány za globálními proměnné skriptu (jsou deklarovány jako atributy automaticky vytvářené třídy skriptu)
► Deklarované proměnné jsou lokálními proměnnými hlavní metody skriptu
(tj. metody run()), a nejsou proto z ostatních metod viditelné
► Chcete-li deklarovat proměnnou jako atribut skriptovací třídy,
dosáhnete toho od verze 1.8 pomocí anotace @Field
● Pozor! Danou anotaci musíte nejprve importovat příkazem import groovy.transform.Field Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
67 z 133
@Field int globInt = 1 //Proměnná deklarovaná jako atribut třídy skriptu def localScr = 1 //Lokální proměnné hlavní metody skriptu global = 1 //Dynamicky vytvořená globální proměnná skriptu //Definice metody skriptu def mtdScr() { try{ println localScr; assert false } //Je lokální v jiné metodě catch(Exception e){ assert true } int localMtd = 2 //Vlastní lokální proměnná této metody global = 2 //Změna hodnoty atributu global globInt = 2 //Změna hodnoty atributu globInt nextGlob = 2 //Zavedení nové globální proměnné } //To byla jen definice, zatím se ještě neprojevila assert localScr == 1; assert global == 1; assert globInt == 1 try{ println ('nextGlob=' + nextGlob) } catch(Exception e){ println 'nextGlob ještě neznám' } mtdScr() //Metoda mimo jiné inicializuje proměnné global a nextGlob try{ println localMtd; assert false } //Lokální proměnná metody mtdScr catch(Exception e){ assert true } assert global == 2; assert globInt == 2; assert nextGlob == 2 Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
68 z 133
► Příklad demonstrující funkci anotace @Field převzatý z http://mrhaki.blogspot.com/2011/04/groovy-goodness-change-scope-script.html
import groovy.transform.Field String stringValue = 'I am typed without @Field.' def i = 42 @Field String stringField = 'I am typed with @Field.' counter = 0 // No explicit type definition. def runIt() { try { assert stringValue == 'I am typed without @Field.' } catch (e) { assert e instanceof MissingPropertyException } try { assert i == 42 } catch (e) { assert e instanceof MissingPropertyException } assert stringField == 'I am typed with @Field.' assert counter++ == 0 } runIt() assert stringValue == 'I am typed without @Field.' assert stringField == 'I am typed with @Field.' assert i == 42 assert counter == 1 Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
69 z 133
3.3 Přetěžování operátorů ► Všechny operátory jsou definovány jako metody, které je možno přetížit
a definovat i pro jiné typy, než pro něž byly definovány původně ● 1 + 1 je totéž jako 1.plus(1)
Operator a + b a – b a * b a / b a % b a++ ++a a‐‐ ‐‐a a**b a | b a & b a ^ b
Name
~a
Bitwise complement
a.negate()
a[b] a[b] = c
Subscript Subscript assignment
a.getAt(b) a.putAt(b, c)
Plus Minus Star Divide Modulo Post increment Pre increment Post decrement Pre decrement Power Numerical or Numerical and Numerical xor
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
Method a.plus(b) a.minus(b) a.multiply(b) a.div(b) a.mod(b)
Works with Number, String, collection Number, String, collection Number, String, collection
a.next()
Number, String, Range
a.previous()
Number, String, Range
a.power(b) a.or(b) a.and(b) a.xor(b)
Number Integral number Integral number Integral number Integral number, String (the latter returning a regular expression pattern) Object, List, Map, String, Array Object, List, Map, StringBuffer,
Number Integral number
70 z 133
Operator
Name
Method
a << b
Left shift
a.leftShift(b)
a >> b a >>> b
Right shift Right shift unsigned
a.rightShift(b) a.rightShiftUnsigned(b)
switch(a) { Classification case b: } a == b a != b a <=> b a > b a >= b a < b a <= b a as type
Equals Not equal Spaceship Greater than Greater than or equal to Less than Less than or equal to Enforced coercion
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
b.isCase(a) a.equals(b) ! a.equals(b) a.compareTo(b) a.compareTo(b) > 0 a.compareTo(b) >= 0 a.compareTo(b) < 0 a.compareTo(b) <= 0 a.asType(typeClass)
Works with Array
Integral number, also used like “append” to StringBuffers, Writer, File, Socket, List Integral number Integral number Object, Range, List, Collection, Pattern, Closure; also used with collection c in c.grep(b), which returns all items of c where
b.isCase(item) Object; consider hashCode() Object java.lang.Comparable Any type
71 z 133
3.3.1 Nové operátory ► Elvisův operátor ?: (nepodařilo se mi zjistit, proč se tak jmenuje) ● Výraz objekt ?: implicitní_hodnota (objekt) ? objekt : implicitní_hodnota je zkrácenou podobou výrazu ● Používá se tehdy, potřebujeme-li zabezpečit, aby se přiřadila nějaká hodnota. Pokud je hodnota vyhodnocena jako false, je výsledkem implicitní hodnota ● Oproti ternárnímu operátoru ušetřím druhé vyhodnocení výrazu
def x = 0, y = x ?: 1; assert y == 1 x = 2; y = x ?: 1; assert y == 2 ► Podmíněný přístup ?. ● Výraz objekt ?. člen (objekt == null) ? null : objekt.člen je zkrácenou podobou výrazu ● Použití operátoru nám ušetří neustále ověřování, zda oslovovaný operand není prázdný odkaz
int a; assert (a ?. is(0)) == null a = 1; assert (a ?. is(0)) == false
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
72 z 133
3.4 Textové řetězce a jejich metody greeting = 'Hello Groovy!' assert greeting.startsWith('Hello') //Test počátečního podřetězce assert greeting.getAt(0) == 'H' assert greeting[0] == 'H' //Indexace znaků ve stringu assert greeting.indexOf('Groovy') >= 0 //Hledání textu assert greeting.contains('Groovy') //Obsahuje text? assert greeting[6..11] == 'Groovy' //Indexace rozsahem assert 'Hi' + greeting ‐ 'Hello' == 'Hi Groovy!' //Oddečítání stringů assert greeting.count('o') == 3 //Počet výskytů řetězce assert 'x'.padLeft(3) == ' x' //Zarovnávání assert 'x'.padRight(3,'_') == 'x__' assert 'x'.center(3) == ' x ' assert 'x' * 3 == 'xxx' //Opakování řetězce Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
73 z 133
3.4.1 Automatizované využívání třídy StringBuffer ► Groovy při práci s textovými řetězce podle potřeby automaticky přepíná
mezi prací s instancemi tříd String a StringBuffer
►
greeting = 'Hello' assert (greeting << ' xxx').toString() == 'Hello xxx' assert greeting == 'Hello' assert greeting instanceof java.lang.String assert (greeting << ' xxx') instanceof java.lang.StringBuffer greeting <<= ' Groovy' assert greeting instanceof java.lang.StringBuffer assert greeting != 'Hello Groovy' //Liší se typy objektů greeting << '!' assert greeting.toString() == 'Hello Groovy!' greeting[1..4] = 'i' //Se stringem by to udělat nešlo assert greeting.toString() == 'Hi Groovy!' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
74 z 133
greeting = 'Hello' try { greeting[1..4] = 'i'; assert false } catch(Exception e) { assert true }
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
75 z 133
3.5 Regulární výrazy ► Once a programmer had a problem.
He thought he could solve it with a regular expression. Now he had two problems.
► Zjistit, zda text plně odpovídá zadanému vzoru ► Zjistit, zda se v daném řetězci vyskytuje obraz daného vzoru ► Zjistit, kolikrát se obraz zadaného vzoru vyskytuje v daném stringu ► Provést něco s každým výskytem obrazu vzoru ► Nahradit všechny výskyty obrazu vzoru nějakým textem,
přičemž tento text může být sám zadán jako regulární výraz a pracovat s obsahem pozměňovaného řetězce
► Rozdělit zadaný řetězce na sadu řetězců,
přičemž oddělovače jednotlivých podřetězců v původním řetězci jsou zadané prostřednictvím regulárního výrazu
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
76 z 133
3.5.1 Základní operátory ► Operátor definice řetězce jako regulárního výrazu: ~
dateFm = /\d\d?([‐.\/])\d\d?\1\d\d(\d\d)?/ datePt = ~ dateFm assert dateFm instanceof java.lang.String && datePt instanceof java.util.regex.Pattern ► Operátor „odpovídání“ (match) řetězce danému výrazu: ==~
výsledkem jeho aplikace je logická hodnota
assert '12‐3‐2011' ==~ datePt //Použit předpřipravený regulární výraz assert '12‐3‐2011' ==~ dateFm //Výraz se musí odvodit z řetězce assert ('12‐3‐2011' ==~ datePt) instanceof Boolean ► Operátor „výskytu“ výrazu v řetězci: =~
výsledkem jeho aplikace je instance třídy java.util.Matcher
pkpk = 'Pan kaplan plakal v kapli' matcher = (pkpk =~ /\b\S*pl\S*\b/) //Slova obsahující text "pl" assert matcher instanceof java.util.regex.Matcher assert matcher.size() == 3 //V textu se vyskytují tři taková slova Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
77 z 133
3.5.2 Realizace vyhledávání, shody a nahrazování ► Hledáme v zadaném textu slova obsahující „pl“
matcher = ('Pan kaplan plakal v kapli' =~ /\b\S*pl\S*\b/) ► V textu se vyskytují tři taková slova
assert matcher.size() == 3 ► S „matcherem“ můžeme pracovat obdobně jako se seznamem
assert matcher[0] == 'kaplan' assert matcher[2] == 'kapli' foundlings = '' matcher.each{ foundlings += it + ' '} assert foundlings == 'kaplan plakal kapli '
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
78 z 133
3.5.3 Práce s jednotlivými výskyty vzoru v řetězci myFairStringy = 'The rain in Spain stays mainly in the plain!' BOUNDS = /\b/ rhyme0 = /$BOUNDS\w*ain$BOUNDS/ rhyme2 = /$BOUNDS(\w*)(ain)$BOUNDS/ found = '' myFairStringy.eachMatch(rhyme0) { match ‐> found += match + ' ' } assert found == 'rain Spain plain ' found = '' (myFairStringy =~ rhyme2).each { match ‐> found += match[0] + ' ' } assert found == 'rain Spain plain ' found = '' myFairStringy. eachMatch(rhymeG) { found += it} assert found == '[rain, r, ain][Spain, Sp, ain][plain, pl, ain]' cloze = myFairStringy.replaceAll(rhyme0){ it ‐ 'ain' + '___' } assert cloze == 'The r___ in Sp___ stays mainly in the pl___!' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
79 z 133
3.5.4 Regulární výrazy v příkazech switch ► Využívá se toho, že pro regulární výrazy je definovaná metoda isCase(Object)
assert (~/aho+j/).isCase('ahoooooj') result = '' ['Franta', 'Pavel', 'Lojza', 'Venouš'].each { switch(it){ case ~/.+a/ : result += "X‐a "; break case ~/.*a.*/ : result += "‐a‐ "; break default : result += "??? " } } assert result == 'X‐a ‐a‐ X‐a ??? ' switch('bear'){ case ~/..../ : assert true; break default : assert false } beasts = ['bear','wolf','tiger','regex'] assert beasts.grep(~/..../) == ['bear','wolf'] Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
80 z 133
3.6 Pracujeme se seznamy List myList = [1, 2, 3] assert myList.size() == 3 assert myList[0] == 1 assert myList instanceof ArrayList myList[0] = 10; assert myList[0] == 10 List emptyList = [] assert emptyList.size() == 0 List longList = (0..1000).toList() //Převod rozsahu na seznam assert longList[555] == 555 List explicitList = new ArrayList() explicitList.addAll(myList) //Kopírování obsahu seznamu assert explicitList.size() == 3 assert explicitList[0] == 10 explicitList = new LinkedList(myList) assert explicitList.size() == 3 assert explicitList[0] == 10 Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
81 z 133
► Převod pole na seznam
//Každý skript zná poměnnou args s parametry příkazového řádku assert args instanceof String[] // Deklarovaná String[] args assert args.size() == 0 assert args == [] //Nic jsme nezadávali, pole je prázdné args = args as List //Konverze pole na seznam args.add('X'); //Přidání prvku do seznamu assert args == ['X'] ► Chceme-li někam vložit prvky seznamu, rozložíme seznam unárním operátorem *;
bez rozkladu bychom nevložili prvky, ale celý seznam
List levels = [0, myList, 4] //Bez rozložení assert levels == [0, [10, 2, 3], 4] List flat = [0, *myList, 4] //Použit operátor * ‐ Spread assert flat == [0, 10, 2, 3, 4] flat = [0, *[1, [2, 3], 4], 5] //Rozloží pouze vrchní úroveň assert flat = [0, 1, [2, 3], 4, 5] Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
82 z 133
3.6.1 Práce s operátorem indexace myList = ['a', 'b', 'c', 'd', 'e', 'f'] assert myList[0..2] == ['a', 'b', 'c'] assert myList[0,2,4] == ['a', 'c', 'e'] myList[0..2] = ['x', 'y', 'z'] assert myList == ['x', 'y', 'z', 'd', 'e', 'f'] myList[3..<5] = [] assert myList == ['x', 'y', 'z', 'f'] myList[1..1] = ['y', '1', '2'] assert myList == ['x', 'y', '1', '2', 'z', 'f'] ► Záporné indexy se počítají odzadu, x[‐1] je index posledního prvku
assert myList[‐1] == 'f' myList[‐1..‐1] = [] assert myList == ['x', 'y', '1', '2', 'z'] assert myList[‐2..‐3] == ['2', '1'] assert myList[‐3..‐2] == ['1', '2'] Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
83 z 133
3.6.2 Operátory přidávání a odstraňování položek ► Vedle operátoru indexace jsou k dispozici i jiné možnosti
List myList = [] myList += 'a' //Přidání objektu na konec seznamu assert myList == ['a'] myList += ['b','c'] //Přidání kolekce assert myList == ['a','b','c'] myList = [] myList << 'a' << 'b' //Pro přidán slouží i operátor posunu assert myList == ['a','b'] assert myList ‐ ['b'] == ['a'] //Odstranění položky assert myList * 2 == ['a','b','a','b'] //Operátor násobení
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
84 z 133
3.6.3 Seznamy v řídících konstrukcích ► Seznamy lze použít i jako položky přepínače
myList = ['a', 'b', 'c'] switch('a') { case myList: assert true; break default: assert false } ► Při vyhodnocování jednotlivých možností se používá metoda isCase
assert myList.isCase('b') //Hodnota 'b' v parametru switch též vyhovuje ► Metoda grep vybere ze zadané kolekce prvky dle kritéria v parametru ● Ze zadaného seznamy vybere prvky, které jsou v seznamu myList
assert ['x','a','z'].grep(myList) == ['a'] ► Prázdný seznam je interpretován jako false, neprázdný jako true
assert mylist //Zatím má stále hodnotu ['a', 'b', 'c'] myList = [] if (myList) assert false //Kdyby byl true, křičel by assert Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
85 z 133
► Seznam je možno použít i jako kolekci v příkazu for
log = '' for (i in [1,'x',5]) { log += i } assert log == '1x5' ► Zjišťování přítomnosti v seznamu prostřednictvím operátoru in
list = [1,2,3] assert 3 in list assert (5 in list) == false
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
86 z 133
3.6.4 Metody manipulující s obsahem ► Množina obsahuje oproti seznamu každý prvek jen jednou –
totéž zabezpečí u seznamu metoda unique()
def x = [1,1,1] assert [1] == new HashSet(x).toList() assert [1] == x.unique() ► V případě potřeby lze seznam převést na množinu
names = ['Dierk', 'Paul'] as Set assert names instanceof Set ► Průnik dvou seznamů, tj. seznam společných prvků
assert [1,2,3].intersect([4,3,1]) == [3,1] ► Potvrzení, že seznamy nemají žádný společný prvek
assert [1,2,3].disjoint([4,5,6])
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
87 z 133
► Metoda flatten() převede svůj seznam na jednoúrovňový
assert [1,[2,[3,4],5],6].flatten() == [1,2,3,4,5,6] ► Operátor * zařídí, aby se nevložil seznam, ale jeho obsah
assert [0, *[1, [2, 3], 4], 5] == [0, 1, [2, 3], 4, 5] ► Je však použitelný pouze uvnitř seznamu, jinak je to syntaktická chyba –
viz druhý řádek následující ukázky
//list2 = [1,[2],3] //list1 = *list2 ► Použití seznamu jako zásobníku (místo push() máme operátor <<)
list = [1,2,3] popped = list.pop() assert popped == 3 assert list == [1,2]
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
88 z 133
► Ovlivňování pořadí
list = [3,2,1] assert list.reverse() == [1,2,3] //Nový seznam s obráceným pořadím prvků assert list == [3,2,1] //Původní seznam se nezměnil list.sort() //Setřídění seznamu však... assert list == [1,2,3] //... změní pořadí jeho prvků ► Třídění se zadaným komparátorem
def list = [ [1,0], [0,1,2] ] list = list.sort { a,b ‐> a[0] <=> b[0] } //Podle prvního prvku položky assert list == [ [0,1,2], [1,0] ] list = list.sort { item ‐> item.size() } //Podle velikosti položky assert list == [ [1,0], [0,1,2] ]
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
89 z 133
► Odstraňování prvků ze seznamu ● remove(int) odstraňuje prvek se zadaným indexem
list = ['a','b','c'] list.remove(2) //Zadali jsme index assert list == ['a','b'] ● remove(Object) odstraňuje prvek se zadanou hodnotou
list.remove('b') //Zadali jsme hodnotu assert list == ['a'] ● Jsou-li prvky seznamu celá čísla, musíme zadávaný parametr přetypovat na Object, aby jej překladač nepovažoval za index a nevybral proto špatnou metodu
list = [10,20,30] list.remove((Object)10) assert list == [20,30]
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
90 z 133
► Odstranění všech výskytů zadaných prvků ze zadané kolekce
list = ['a','b','b','c'] list.removeAll(['b','c']) assert list == ['a'] ► Pro odstranění všech výskytů můžeme použít i odečítání
list = ['a','b','b','c','c','c','d'] list ‐= ['b','c'] assert list == ['a','d'] ► Obdobně můžeme ze seznamu vyjmout prázdné odkazy
x = [1,null,null,2] assert [1,2] == x.findAll{it != null} //Vytvoříme seznam nenullových prvků assert [1,2] == x.grep{it} //Využijeme, že null ≈ false assert [1,2] == x ‐ [null] //Použijeme odečítání x.removeAll([null]) //Metoda mění x a vrací boolean assert [1,2] == x //Proto musíme testovat x
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
91 z 133
3.6.5 Aplikace kódu na prvky seznamu ► Aplikace zadaného kódu na všechny prvky – metoda each(Closure)
store = '' [1,2,3,4].each{ store += it } assert store == '1234' ► Aplikace zadaného kódu na všechny prvky odzadu – metoda reverseEach(Closure)
store = '' [1,2,3,4].reverseEach{ store += it } assert store == '4321' ► Vytvoření nové kolekce ze zpracovaných prvků – metoda collect(Closure) ● each(Closure) prvky jen zpracovává, tato ze zpracovaných navíc vytváří novou kolekci ● Ani jedna z uvedených metod nemění původní kolekci
list = [1,2,3] list2 = list.collect{item ‐> item*10} //Nový seznam se změněnými prvky assert list2 == [10,20,30] assert list == [1,2,3]
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
92 z 133
► Aplikace zadaného kódu na všechny prvky s dodáním indexu
store = '' [1,2,3].eachWithIndex { item, index ‐> store += "$index:$item " } assert store == '0:1 1:2 2:3 ' ► Aplikace zadaného kódu s „průtočným“ parametrem
se zadanou počáteční hodnotou – inject(Object,Closure) ● Kód má dva parametry: první je „průtočný“, druhým je aktuální prvek
result = [1,2,3,4].inject(0){ sum, item ‐> sum += item } assert result == 0 + 1+2+3+4 report = [1,2,3,4].inject('List items: '){ txt, item ‐> txt += item + ", " } assert report == 'List items: 1, 2, 3, 4, ' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
93 z 133
► Rozdělí obsah do dvou seznamů podle zadaného kritéria ● Vrátí kolekci s těmito seznamy ● Následující program ukazuje možnost rozdělení kolekce do proměnných
def list = [0, 3, 2, 1] assert [[0,1],[3,2]] == list.split { it < 2 } def small, big (small, big) = list.split { it < 2 } assert small == [0, 1] assert big == [3, 2] ► Vytvoří mapu se skupinami klíčovanými hodnotami kritéria
def group = [0,2,4,6,8,10].groupBy { it % 10 } assert group instanceof Map assert group.size() == 5 assert group[0] == [0, 10] assert group[4] == [4] assert group[5] == null ► Vytvoří String z podpisů prvků oddělených zadaným oddělovačem
assert [1,2,3,4].join('‐=‐') == '1‐=‐2‐=‐3‐=‐4' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
94 z 133
3.6.6 Dotazování a vyhledávání ► Dotazy na vlastnosti seznamu a jeho prvků
list = [1,2,1,3,1,4] assert list.count(1) == 3 //Počet výskytů zadané hodnoty assert list.max() == 4 //Maximální prvek assert list.min() == 1 //Minimální prvek assert list.sum() == 12 //Součet prvků assert list.first() == 1 //První prvek assert list.head() == 1 //Hlava = prvnmí prvek assert list.tail() == [2,1,3,1,4] //Ocas = seznam po odebrání hlavy assert list.last() == 4 //Poslední prvek ► Dotazovacím metodám lze dodat i kód jak vyhodnocovat ● Následující kód porovnává položky podle délky a sčítá délky položek
kings = ['Dierk', 'Paul'] assert kings.max { item ‐> item.size() } == 'Dierk' assert kings.min { item ‐> item.size() } == 'Paul' assert kings.sum { item ‐> item.size() } == 9 Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
95 z 133
► Vrátí první prvek vyhovující zadanému kritériu
even = list.find { it % 2 == 0 } assert even == 2 ► Zjistí, zda (všechny prvky) / (alespoň jeden prvek) vyhovují zadanému kritériu
assert list.every { item ‐> item < 5} //Všechny prvky vyhovují assert list.any { item ‐> item > 3} //Alespoň jeden prvek vyhovuje ► Vytvoření nové kolekce z prvků vyhovujících zadanému kritériu ● findAll(Closure), kde parametr vrací výsledek vyhodnocení prvku
odd = [1,2,3,4,5].findAll{item ‐> item % 2 == 1} assert odd == [1,3,5] ● grep(Object), kde se na prvek aplikuje Object.isCase(it); je-li parametrem Closure, pracuje stejně jako findAll(Closure)
odd = [5,4,3,2,1].grep{ it % 2 == 1 } assert odd == [5, 3, 1]
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
96 z 133
3.6.7 Příklad: Definice algoritmu QuickSort def quickSort(list) { if (list.size() < 2) return list def pivot = list[list.size().intdiv(2)] def left = list.findAll {item ‐> item < pivot } def middle = list.findAll {item ‐> item == pivot } def right = list.findAll {item ‐> item > pivot } return (quickSort(left) + middle + quickSort(right)) } assert quickSort([]) == [] assert quickSort([1]) == [1] assert quickSort([1,2]) == [1,2] assert quickSort([2,1]) == [1,2] assert quickSort([3,1,2]) == [1,2,3] assert quickSort([3,1,2,2]) == [1,2,2,3] assert quickSort([1.0f,'a',10,null])== [null, 1.0f, 10, 'a'] assert quickSort('Karin and Dierk') == ' DKaadeiiknnrr'.toList()
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
97 z 133
3.7 Pracujeme s mapami def myMap = [a:1, b:2, c:3] assert myMap instanceof LinkedHashMap //Zaručuje pořadí assert myMap.size() == 3 assert myMap['a'] == 1 //Zadáním hodnoty klíče assert myMap.b == 2 //Dobře pojmenovaný klíč lze použít jako atribut map2 = new TreeMap() map2.putAll(myMap) //Vložení obsahu jedné mapy do druhé assert map2 == myMap map2 = [:] //Definice prázdné mapy assert map2.size() == 0 assert map2 != myMap map2 = new TreeMap(myMap) //Nová mapa je kopií původní assert map2 == myMap composed = [x:'y', *:myMap] //Použití operátoru * při vkládání mapy do jiné assert composed == [x:'y', a:1, b:2, c:3] Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
98 z 133
► Je-li klíč typu String, lze jej používat jako identifikátor;
musí však vyhovovat podmínkám pro identifikátory
assert ['a':1] == [a:1] //Stringový klíč a odpovídající atribut String x = 'a' //Název atributu/klíče lze uložit i do proměnné assert ['a':2] == [(x):2] //Při použití jej však musíme nejprve vyhodnoti assert ['x':1] == [x:1] //Zde použité x je přímo názvem klíče mapy
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
99 z 133
3.7.1 Přístup k položkám mapy ► Mapa, s níž budeme pracovat
def myMap = [a:1, b:2, c:3] ► Pro získání položky můžeme mapu buď indexovat klíčem,
nebo tento klíč použít jako by to byl atribut
assert myMap['a'] == 1 //Indexace klíčem assert myMap.a == 1 //Klíč použitý jako atribut assert myMap.get('a') == 1 //Získání hodnoty pomocí metody assert myMap.get('a',0) == 1 //Volání s hodnotou dosud neexistující položky ► Poslední metoda nabízela hodnotu pro případ,
kdy by položka s daným klíčem neexistovala
assert myMap['d'] == null //Položka s daným klíčem neexistuje assert myMap.d == null //Stejný výsledek assert myMap.get('d') == null //Stejný výsledek assert myMap.get('d',4) == 4 //Byla zřízena položka assert myMap.d == 4 // se zadaným klíčem a hodnotou Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
100 z 133
► Prostřednictvím zadání klíče v indexu či atributu můžeme položku i měnit
myMap['d'] = 1 assert myMap.d == 1 myMap.d = 2 assert myMap['d'] == 2 ► V indexu musíme zadávat klíč jako String,
jinak bude překladač hledat proměnnou s daným názvem
try { myMap[d] == 2 //Chyba, proměnná d neexistuje assert false; //Sem se nikdy nedostane }catch (Exception e) { assert true //Za běhu vyhodil výjimku } ► Při překladu by předchozí příklad vyhodil syntaktickou chybu,
ve skriptu se však vše zjišťuje až za chodu a teprve tehdy program zjistí, že žádná proměnná d neexistuje
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
101 z 133
► Jako atributy můžeme používat i klíče, které nevyhovují požadavkům
kladeným na identifikátory; musíme je ale zadávat jako řetězce
myMap.get('a.b', 5) //Klíč nevyhovující požadavkům na identifikátory assert myMap.'a.b' == 5 //Neidentifikátorový klíč jako atribut assert myMap == [a:1, b:2, c:3, d:2, 'a.b':5] //Současná podoba mapy
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
102 z 133
3.7.2 Informace o mapě ► Mapy, s nimiž budeme pracovat
def myMap = [a:1, b:2, c:3] def other = [b:2, c:3, a:1] ► Při porovnávání map nezáleží na pořadí zadání jednotlivých položek
assert myMap == other ► Základní informace o mapě
assert myMap.isEmpty() == false assert myMap.size() == 3 assert myMap.containsKey('a') assert myMap.containsValue(1) ► Z mapy umíme vytáhnout množinu položek, množinu klíčů
assert myMap.entrySet() instanceof Set assert myMap.keySet() == ['a','b','c'] as Set
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
103 z 133
► Při získávání kolekce hodnot můžeme využít toho,
že mapy jsou implicitně instance třídy LinkedHashMap, a respektují proto pořadí zadávání jednotlivých položek
assert myMap.values().toList() == [1, 2, 3] assert other.values().toList() == [2, 3, 1] ► I u map můžeme zjišťovat platnost nějakých pravidel pro jejich položky ● Má-li kód jediný parametr, je jím položka mapy = instance Map.Entry
assert myMap.any {entry ‐> entry.value > 2 } assert myMap.every {entry ‐> entry.key < 'd'} ● Má-li kód dva parametry, jsou jimi klíč a hodnota dané položky
assert myMap.any {key, value ‐> value > 2 } assert myMap.every {key, value ‐> key < 'd'}
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
104 z 133
3.7.3 Aplikace kódu na položky v mapě ► Stejně jako u zjišťovacích metod any a every, tak i u akčních metod
existují pro mapy dvě možnosti jak zadat prováděný kód
def myMap = [a:1, b:2, c:3] def store = '' myMap.each {entry ‐> store += entry.key; store += entry.value } assert store == 'a1b2c3' myMap.each {key, value ‐> store += value; store += key } assert store == 'a1b2c31a2b3c' for (entry in myMap.entrySet()) { store += ", "; store += entry; } assert store == 'a1b2c31a2b3c, a=1, b=2, c=3' Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
105 z 133
► Některé metody (např. metoda inject) však obě možnosti neposkytují
a můžeme používat pouze jeden parametr představující celou položku
result = myMap.inject('') {out, entry ‐> //Dvouparametrická pracuje out += "($entry.key:$entry.value)" } assert result == '(a:1)(b:2)(c:3)' try { //Pokus o použití tříparametrické zhavaruje result = myMap.inject('') {out, key, value ‐> } assert false //Sem to nepřijde, zpracování akce vyhodí výjimku } catch (Exception e) { assert true //Tříparametrická verze neexistuje }
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
106 z 133
3.7.4 Práce s obsahem ► Potřebujeme-li vyjmout prvek z mapy, musíme se na něj odkázat hodnotou klíče,
převod případného stringu ne identifikátor nefunguje
def myMap = [a:1, b:2, c:3] myMap.remove('a') assert myMap == [b:2, c:3] ► Vyprázdnění mapy
myMap.clear() assert myMap.isEmpty() ► Slučováním obsahů map se nemění výchozí mapy, ale vzniká mapa nová
m1 = [a:1]; m2 = [b:2] myMap = m1 + m2 assert m1 == [a:1] assert m2 == [b:2] assert myMap == [a:1, b:2] Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
107 z 133
► Vytvoření nové mapy z položek se zadanými klíči
myMap = [a:1, b:2, c:3] def genMap = myMap.subMap(['a','b']) assert genMap == [a:1, b:2] ► Najde první položku vyhovující zadanému kritériu
def found = myMap.find { entry ‐> entry.value < 2} assert found instanceof Map.Entry assert found.key == 'a' assert found.value == 1 ► Vytvoření kolekce ze zpracovaných položek mapy ● POZOR! Ukázka předvádí, jak lze vzhledem k proměnnosti instancí položek změnit jako vedlejší efekt i hodnoty těchto položek
myMap = [a:1, b:2, c:3] genCol = myMap.collect {entry ‐> entry.value *= 2} //Mění se hodnota položky assert genCol instanceof List //Vytvořená kolekce je seznam assert myMap == [a:2, b:4, c:6] assert genCol == [ 2, 4, 6]
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
108 z 133
► Přidání zpracovaných hodnot do zadané kolekce ● Tato ukázka se nesnaží měnit hodnoty položek mapy
myMap.collect(genCol) { entry ‐> entry.value * 4} assert myMap == [a:2, b:4, c:6] assert genCol == [ 2, 4, 6, 8, 16, 24] ► Mapu lze vydávat za objekt, jehož atributy/metody se jmenují jako příslušné klíče =>
v následující ukázce lze vydávat mapu za komparátor, protože: ● Má položku pojmenovanou compare ● Tato položka je metodou se dvěma objektovými parametry
def absComp = [ compare: { a,b ‐> a.abs() <=> b.abs() } ] def list = [‐3, ‐1, 2] list.sort(absComp as Comparator) assert list == [‐1, 2, ‐3]
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
109 z 133
3.8 Možnosti příkazu switch ► Testovatelné případy můžeme ovlivnit definici metody isCase(Object) ► Kód zapsaný ve tvaru:
switch (candidate) { case classifier1 : handle1() ; break case classifier2 : handle2() ; break default : handleDefault() } je plně ekvivalentní s kódem: if (classifier1.isCase(candidate)) handle1() else if (classifier2.isCase(candidate)) handle2() else handleDefault()
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
110 z 133
► Následující program ukazuje možné použití přepínače v Groovy
switch (10) {//Zadaný argument //není zadanou hodnotou case 0 : assert false ; break //nespadá do zadaného rozsahu case 0..9 : assert false ; break //není hodnotou v zadaném seznamu case [8,9,11] : assert false ; break //není instancí zadané třídy case Float : assert false ; break //vyhodnocení zadaného uzávěru nevrátí true case {it%3 == 0}: assert false ; break //po převedení na řetězce je tvořen dvěma znaky case ~/../ : assert true ; break //Alespoň jedna z podmínek prošla default : assert false ; break }
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
111 z 133
3.9 Metoda with(Closure) ► Uvnitř uzávěru předaného metodě with(Closure) mají přednost
deklarované proměnné před atributy objektu, jehož metoda with je volána ● Důvody si vysvětlíme v příští kapitole věnované uzávěrům
def map = [a:1, b:2] def b = 20 map.with() { assert a == 1 assert b == 20 }
KONEC kapitoly Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
112 z 133
4.
Uzávěry
Obsah 4.1 Definice 4.2 Různé způsoby definice 4.3 Použití uzávěrů 4.3.1 Umožňuje definovat metody vystupující jako programové konstrukce 4.3.2 Použití v programových konstrukcích 4.3.3 Postupné předávání parametrů – currying 4.4 Obory viditelnost a dosažitelnost 4.4.1 Opakované volání – akumulátor
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
113 z 133
4.1 Definice ► Uzávěr je část kódu uzavřená do objektu ● Pracuje jako metoda, které můžeme předat parametry ● Je to standardní objekt => ● Můžeme jej uložit do proměnné ● Mohou na něj odkazovat jiné objekty ● Může se odkazovat na jiné objekty ► Kdykoliv uvidíte složené závorky označující uzávěr,
myslete si, že vidíte new Closure(){ ... }
► V klasické Javě se uzávěry nahrazují anonymními třídami ● Jsou uvnitř svých tříd, a mají proto přístup k jejich soukromým položkám ● S instancí anonymní třídy exportujeme také schopnost provést akci, k níž je znalost vnitřního uspořádání objektu potřebná ● Příklad použití: iterátor, posluchači událostí ► Groovy na rozdíl od Javy a jejích anonymních tříd nelpí na tom,
aby uzávěry používaly z okolního kódu jen konstanty – uzavřou si do svého objektu vše, co budou potřebovat pro vykonání kódu
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
114 z 133
4.2 Různé způsoby definice ► Kód uzávěru je možné uložit do proměnné,
tu pak můžeme předat jako parametr metodě
def con = '' Closure concat = { con += it + ', ' } (1..5).each concat assert con == '1, 2, 3, 4, 5, ' ► Kód můžeme volat i přímo, a to hned několika způsoby:
concat(4); assert con == '1234' //Přímým použitím dané proměnné concat.call(5); assert con == '12345' //Voláním metody call concat.doCall(6); assert con == '123456' //Voláním metody doCall ► Metoda call(Object...) má akceptovatelné parametry definované obecněji,
metoda doCall(???) má parametry definované podle definice uzávěru; v obou případech se však akceptovatelnost parametrů vyhodnocuje až za běhu
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
115 z 133
► Uzávěr je možno předávat jako parametr metody –
objevují se tak zdánlivě nové syntaktické konstrukce
result = '' def lockOn () { result += 'locked{ ' } def lockOff() { result += ' }unlocked' } //Definujeme metodu zabezpečující chod svěřeného uzávěru def locked(Closure c) { lockOn() c() lockOff() } locked { //"Nová" syntaktická konstrukce: zabezpečený chod result += 'Closure called' } assert result == 'locked{ Closure called }unlocked'
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
116 z 133
► Pomocí operátoru & lze jako uzávěr lze definovat i běžnou metodu
class MethodClosureSample { int limit //Délka řetězce, který filtr propustí jako platný MethodClosureSample (int limit) { this.limit = limit } boolean validate (String value) { //Filtr pro budoucí hledání return value.length() <= limit } } //Definuji objekty s přednastavenou délkou pro jejich filtry def MethodClosureSample first = new MethodClosureSample (6) def MethodClosureSample second = new MethodClosureSample (5) def Closure firstClosure = first.&validate //Metoda definovaná jako uzávěr def words = ['long string', 'medium', 'short', 'tiny'] assert 'medium' == words.find (firstClosure) //Použij uzávěr v proměnné assert 'short' == words.find (second.&validate)//Uzávěr vytvoř na místě Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
117 z 133
► Operátor & umožňuje přiřadit i přetíženou metodu,
přičemž toto přetížení funguje i při volání daného uzávěru
class MultiMethodSample { int method (String value) { return value.length() //Vrací délku řetězce } int method (List list) { return list.size() //Vrací počet prvků seznamu } int method (int x, int y) { return x+y //Vrací součet argumentů } } def MultiMethodSample instance = new MultiMethodSample() def Closure multi = instance.&method assert 10 == multi ('string arg') //Délka řetězce assert 3 == multi (['list', 'of', 'values']) //Počet prvků seznamu assert 14 == multi (6, 8) //Součet argumentů Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
118 z 133
4.3 Použití uzávěrů ► Používáme-li použít uzávěr jako parametr metody, je vhodné jej umístit na konec,
protože pak jej můžeme uvést zvlášť, tj. ne mezi ostatními parametry
def benchmark(repeat, Closure worker){ start = System.currentTimeMillis() repeat.times{worker(it)} stop = System.currentTimeMillis() return stop ‐ start } slow = benchmark(10000) { (int) it / 2 } fast = benchmark(10000) { it.intdiv(2) } assert fast * 10 < slow ► Třída Closure definuje pro své instance řadu metod umožňujících jejich používání
zefektivnit a zpohodlnit → např. znalost počtu parametrů se hodila při obsluze map
def caller (Closure closure) { closure.getParameterTypes().size() } assert caller { one ‐> } == 1; assert caller { one, two ‐> } == 2 Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
119 z 133
4.3.1 Umožňuje definovat metody vystupující jako programové konstrukce ► Konstrukce if … then a while mají podobu dvouparametrických metod,
jejichž prvním parametrem je podmínka a druhým uzávěr
► Definujeme-li vhodně metodu, jejímž posledním parametrem j uzávěr,
může její použití připomínat programovou konstrukci –> používá se při definici DSL (Domain Specific Language)
● Příkladem je např. metoda with– viz kapitolu 3.9 Metoda with(Closure)
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
120 z 133
4.3.2 Použití v programových konstrukcích ► Příprava demonstračního uzávěru
def odd = { it % 2 == 1} assert [1,2,3].grep(odd) == [1, 3] ► Pro uzávěry definovaná metoda isCase(Object), a to tak,
že jim předá svůj parametr a vrátí jejich výsledek => uzávěry je možno používat v návěštích case
switch(10) { case odd : assert false //Není liché => tudy nesmí projít default : assert true } ► Obdobně operátor in může mít jako svůj pravý operand uzávěr,
kterému pak při vyhodnocení předá svůj levý operand jako parametr
if (2 in odd) assert false
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
121 z 133
4.3.3 Postupné předávání parametrů – currying ► Technika je pojmenována po průkopníku funkcionálního programování
Haskell Brooks Curry (1900..1982)
► Uzávěr s více parametry se po vyhodnocení některého z nich
převede na uzávěr s méně parametry
def adder = { x, y ‐> return x+y } //Uzávěr se dvěma parametry def addOne = adder.curry(1) //Uzávěr s jedním parametrem assert addOne(5) == 6 //Použití jednoparametrického uzávěru ► Ukázky curryingu na příkladu logování:
def logger = { formatter, appender, line ‐> //Obecná logovací metoda appender(formatter(line)) } def rightFormatter = { line ‐> line.padLeft(25) + "\n" } //Zarovná vpravo def collector = new StringBuilder() //Kolektor def stringAppender = { line ‐> collector << line } //Vlastní zápis def myLog = logger.curry(rightFormatter, stringAppender) //VýslednáSestava myLog 'here is some log message' //Použití assert collector.toString().contains('log message') //Prověrka výsledku Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
122 z 133
► Parametry je třeba kumulovat v tom pořadí, v jakém jsou uvedeny v definici uzávěru ► Postupná kumulace parametrů umožňuje
efektivně modifikovat vykonávaný kód za běhu programu.
► Alternativní verze logovacího programu nevyužívající currying:
lass GeneralLogger { Closure formatter = { line ‐> line } //Obdobnou služu poskytnou atributy Closure appender = { } //|#1 Closure logger = { line ‐> appender(formatter(line)) } } def out = new StringBuilder() def newLog = new GeneralLogger( //Pojmenovaním přímo přiřazuje hodnoty atributům formatter : { line ‐> line.padLeft(25) + "n" }, appender : { line ‐> out << line } ).logger //Přiřadí proměnné odkaz na loger vužívající svých kolegů newLog 'here is some log message' //Použití assert out.toString().contains('log message') //Prověrka výsledku
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
123 z 133
4.4 Obory viditelnost a dosažitelnost ► Z uzávěru jsou dostupné entity v jeho oboru dostupnosti;
obor dosažitelnosti definuje:
● Které lokální proměnné jsou dostupné ● Na co se odkazuje this ● Které atributy a metody jsou dostupné ► Z uzávěru jsou dostupné lokální proměnné jeho vnější metody,
tj. metody, v níž je definován (metody vlastníka uzávěru)
► Porovnání dostupnosti proměnných z metody a z uzávěru ve skriptu:
def txt ='TXT:', x=0 //txt a x jsou lokální proměnné metody run 10.times { txt += x++ } //Uzávěr vidí na lokálmí proměnné své vnější metody assert txt == 'TXT:0123456789' //Nelze definovat metodu v metodě, je definována "vedle" metody run void method () { //Překlad projde println bflmpsvz + x //Chyba ‐ použití lokální proměnné cizí metody } //method() //Chyba se projeví až při zavolání metody Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
124 z 133
► Na rozdíl od interních tříd uzávěry v Groovy neoznačují identifikátorem this sebe,
ale objekt svého vlastníka, jenž je navíc uložen v atributu owner
class Mother { def field = 'f' //Atribut def method(){return "M+$field"} //Metoda Closure birth (param) { //Uzávěr def local = "L+$param" //Uzávěry mohou mít své lokální proměnné //Uzávěr může definovat svůj "lokální uzávěr" def closure = { caller ‐> //Parametrem bude volající objekt [ field: field , method : method(), param : param, local : local, self : this, caller : caller, owner : owner, delegate: delegate] } return closure //Tento uzávěr vrací svůj vlastní uzávěr } } Mother julia = new Mother() //Nová instance s uzávěrem closure = julia.birth(4) //Zapamatujeme si uzávěr vrácený uzávěrem //Uzávěr je teprve definován, ještě nebyl volán julia.field = 'F' //Změním hodnotu atributu context = closure.call(this) //Získaný uzávěr zavoláme Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
125 z 133
► Otestujeme atributy vráceného uzávěru
assert context.field == 'F' //Pole vnější třídy je dosažitelné assert context.method == 'M+F' //Metoda třídy vnějšího uzávěru assert context.param == 4 //Parametr vnějšího uzávěru daného uzávěru assert context.local == 'L+4' //Lokální proměnná vnějšího uzávěru assert context.delegate== julia //Objekt, jehož členy netřeba kvalifovat assert context.self == julia //Kam odkazuje this assert context.owner == julia //Majitel je totožný s this assert context.caller == this //Volajícím objektem je tento skript closure2 = julia.birth(4) //Druhé zavolání vytvoří uzávěr nový, assert closure != closure2 //... který proto není totožný s původním ► V uzávěru lze definovat: ● owner – vlastník = do čí jmenné oblasti je vidět ● delegate – komu také mohou patřit členy, které nemají kvalifikaci ► Obecně je vlastníkem i delegátem objekt, který daný uzávěr zavolal ► V případě potřeby je možné vlastníka i delegáta nastavit jinak
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
126 z 133
► Při testu atributů uzávěru můžeme využít metody with,
musíme však respektovat několik nestandardních skutečností
//Zapamatujeme si odkaz, protože uvnitř uzávěru metody with()... def c2 = context //... bude context==null context.with() { assert field == 'F' //Základní testy jsou shodné s předchozími assert method == 'M+F' assert param == 4 assert local == 'L+4' assert self == julia //Kam odkazovalo this v uzávěru assert caller == this //I zde je volajícím objektem tento skript assert delegate != c2.delegate //Bavíme se o delegátu jiného uzávěru assert delegate == c2 //Adresát vanilkových odkazů tohoto uzávěru //je objekt, jehož metodu with jsme volali //A nyní slibované nestandardnosti assert context == null //Uvnitř je opravdu context == null assert owner != c2.owner //Majitelem tohoto uzávěru je někdo jiný ‐ } // je nastaven metodou with Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
127 z 133
4.4.1 Opakované volání – akumulátor ► Zadání: definujte metodu vracející akumulátor inkrementující zadaný parametr ● Metoda má převzít počátečního hodnotu vytvářeného akumulátoru a vrátit kód (= uzávěr), který po každém zavolání přičte k akumulátoru hodnotu svého parametru ● Využijeme toho, že parametry metod jsou jen speciální případy jejich lokálních proměnných, které se od těch běžných liší pouze v tom, že je inicializuje volající kód ● Uzávěr proto bude pouze přičítat svůj parametr k lokální proměnné své rodné metody
def create(acc) { return { acc += it } } def accumulator = create(10) assert accumulator(2) == 12 assert accumulator(10) == 22
§ Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
128 z 133
6.
KONEC
§
Groovy.doc, verze 0.02.2412, uloženo po 3.12.2012 08:45
133 z 133