Alprogramok
Az alprogramok definiálása a Groovyban néhány vonatkozásában eltér a Java-ban megszokottól. Például lehetőségünk van operátorok felüldefiniálására vagy úgynevezett Closure-ok használatára.
Függvények
A függvények hasonlók a closure-ökhöz (lásd lent), de nem érhetik el a környező (globális) változókat, és nem lehet egymásba ágyazni őket. Rekurzív hívások (ha a függvény a törzsében saját magát hívja meg) viszont megengedettek.
Függvényeket a 'def' kulcsszóval kell definiálni:
def f(){
a = 1
c = { 'here, again' }
c()
}
assert f() == 'here, again'
//g(){ println 'there, again' } //fordítási hiba lenne: a def kulcsszó kötelező
Speciális szintaxis egy függvény másik változóhoz rendelésére:
def f(){ 77 } //függvény definiálása az 'f' névvel
assert f() == 77
def g = this.&f //speciális szintaxis aminek segítségével egy változónak értékül adható egy függvény
assert g() == 77
def h = g //itt nem kell
assert h() == 77
f = 'something else' //ez az 'f' egy VÁLTOZÓ, és nem a függvény NEVE
assert f() == 77 //függvények neveit nem lehet máshoz értékül adni
Függvényeket egyértelműen azonosítja a nevük és paramétereik száma (a paraméterek nem típusosak):
def foo(value){ 'v1' }
def foo(list, value){ 'v2' }
assert foo(9) == 'v1'
assert foo([], 1) == 'v2'
A függvények utolsó paramétereinek lehet alapértelmezett értéket adni:
def dd( a, b=2 ){ "$a, $b" }
assert dd( 7, 4 ) == '7, 4'
assert dd( 9 ) == '9, 2'
Függvény hívás argumentumait le lehet nevezni, és ki lehet értékelni úgy is, mintha kulcsértékek lennének egy 'map'-ben:
def f(m, i, j){ i + j + m.x + m.y }
assert f(6, x:4, y:3, 7) == 20
def g(m, i, j, k, c){ c(i + j + k, m.x + m.y) }
assert g(y:5, 1, 2, x:6, 3){a,b-> a * b } == 66
Változó számú paramétert úgy adhatunk meg, hogy az utolsó argumentum elé írjuk az 'Object[]'-et. Í függvény törzsében, ekkor ez a paraméter bejárható a '.each' operátorral:
def c( arg, Object[] extras ){
def list= []
list<< arg
extras.each{ list<< it }
list
}
assert c( 1 ) == [ 1 ]
assert c( 1, 2, 3, 4 ) == [ 1, 2, 3, 4 ]
Closures
A Groovy Closure speciális programegység. Tekinthetjük egy függvényre mutató pointernek vagy egy "kód blokk"nak is.
Megint más megfogalmazásban: egy névtelen függvény, ami hivatkozhat a lexikális szkópjában található változókra.
A Groovy által bevezetett closure a Ruby és Smalltalk blokkjának és a Lisp lambdájának felelnek meg.
Gyakori, hogy a Groovy programozók ugyanúgy használják a closure-okat mint a java névtelen beágyazott osztályokat.
A Closure-ok egymásba ágyazhatók
Fő forrás a Groovy dokumentáció. Letölthető
innen .
A letöltéseknél egy dokumentációt tartalmazó build-et vagy release-t kell választani.
closure-ökről általában:
itt
(Wiki)
Closures - szintaktika
{ [closureArguments->] statements }
A kapcsos zárójelről a Groovy fordítója tudja, hogy a benne lévő kifejezést programkódként kezelje.
Egy egyszerű paraméter nélküli példa, ahol bemutatjuk, hogy egy closure használhat a belsejében definiált lokális változókat is:
printer = { def localString = "Hello, world!"
println localString
}
A fenti closure, mint egy függvény, "printer()" híváskor kiírja a képernyőre hogy "Hello world!".
Következő példa: paraméter-használat:
square = {x -> x*x}
closure deklaráció és definíció után a square(9) hívás 81-et fog adni.
Implicit paraméterként használhatjuk az "it" azonosítót. Ezt akkor használhatjuk ha a closure-nak egy bejövő paramétere van. Az 'it' ezt a paramétert jelenti. Tehát a "square"-t átírhatjuk egy egyszerűbb formára így:
square = {it * it}
Használjuk most ezt a "square" annak bemutatására, hogyan adhatunk egy closure-t egy függvénynek paraméterül:
[ 1, 2, 3, 4 ].collect(square)
Ekkor a tömb elemei implicit paraméterként átadódnak a closure-nak aki mindegyikre elvégzi a szükséges műveletet és a kimenet a következő lesz:
[ 1, 4, 9, 16 ]
További metódusokért, amik closure-t kaphatnak paraméteréül érdemes megnézni a Grooy GDK dokumentációt.
itt
A következő, bonyolultabb példában bemutatjuk a névtelen closure használatát, valamint, az interakciót egy globális változóval:
myMap = ["asdf": 1 , "qwer" : 2, "sdfg" : 10]
result = 0
myMap.keySet().each( { result+= myMap[it] } )
println result
A fenti kód a myMap-ben tárolt értékeket adja össze.
És lássunk még egy példát, osztályokkal:
class A {
def x = 0
def incrementer(y) {
return { x = x + y }
}
}
a = new A();
incby5 = a.incrementer(5);
assert a.x == 0
incby5()
assert a.x == 5
A closureok szintaktikája ütközhet a sima blokk szintaktikájával, ezért Groovyban a blokkokat meg kell címkézni.
Nincs alapértelmezett viselkedés, a kétértelműséget fel kell oldani. Címkézéssel írhatunk blokkot:
def f(x) {
L: { println x }
}
Explicit paraméter megadásával closuret:
def f(x) {
{ it -> println x }
}
Closures - szemantika
A closureok funkcionalitása helyettesíti a beágyazott osztályok funkcionalitását, sőt
sokkal nagyobb a kifejező erejük, mint amit a beágyazott osztályok nyújtanak.
- A closure-oknak egyetlen implicit metódusuk van, amit doCall()-nak hívnak.
- Egy closure-t végrehajthatunk a call() metódusának meghívásával, vagy a speciális szintakszissal, a () névtelen invokációval.
Mindkét fajta hívást a Groovy egy doCall() hívássá fordít.
- A closure-oknak 1..N argumentumuk lehet, amik opcionálisan lehetnek statikusan típusosak.
Az első paraméter elérhető egy implicit típus nélküli argumentumon keresztül, ha nincs explicit argumentum megadva. Ennek neve it.
Ha nincs paraméter deklarálva és a hívó nem ad meg egyetlen argumentumot sem, akkor az it értéke null lesz.
- Természetesen, ha megadunk paramétereket, akkor az első paraméter a paraméterlista első eleme lesz és nem az it.
- A closureoknak mindig van visszatérési értékük. Ez jöhet egy explicit return utasításból, vagy a closure törzsének utolsó utasításából (azaz az explicit “return” utasítás opcionális).
- A closure hivatkozhat bármely változóra a lexikális szkópjában. Az ilyen változókra azt mondjuk, h a closurehoz vannak kötve.
- A closurehoz kötött változók elérhetőek a closure számára akkor is, ha a closure objektumot a létrehozását tartalmazó szkópon kívül hívják meg
- A closureok normális objektumok a Groovyn belül, és mindig a Closure osztályból származnak.
A kód ami closureokat használ típus nélküli vagy Closure típusú változóként hivatkozhat rájuk.
- A closureok törzse csak explicit hívásnál hajtódnak végre, azaz a closureok nem hívódnak meg definiálásukkor.
- A closureoknak le lehet fixálni az argumentumait és így specializált closureokhoz jutunk (currying)
Továbbá, a closure-ok mindig névtelenek, (ellentétben a Java és a Groovy osztályokkal.)
Típus nélküli vagy Closure típusú változók állíthatók referenciaként egy closure-ra, és így függvényeknek, vagy más closure-oknak paraméterként átadhatók. A closure-nak mindig van visszatérési értéke, mint azt az 5. pontnál is láthattuk, az utolsó utasítás értéke. Ha ennek az utolsó utasításnak nincs visszatérő értéke, akkor a closure "null"-al tér vissza.
Végül egy példa Curryingre:
def c = { arg1, arg2-> println "${arg1} ${arg2}" }
def d = c.curry("foo")
d("bar")
A "d" closure egy specializált closure, ahol az első argumentum mindig felveszi a "foo" értéket. Épp ezért a kiment: "foo bar" lesz.
Operátor túlterhelés
A Groovy támogatja az operátor-túlterhelést, így egyes adatszerkezeteket könnyebben használhatunk. A különféle operátorok a groovyban ekvivalensek következőkben felsorolt nevű függvényekkel. Ezért ha egy osztály írásakor felüldefiniáljuk a megfelelő (kötött nevű) függvényt, azzal felüldefiniáltuk magát az operátort is. A fejezet végén példát is találhatunk.
Operator |
Method |
a+b |
a.plus(b) |
a-b |
a.minus(b) |
a*b |
a.multiply(b) |
a**b |
a.power(b) |
a/b |
a.div(b) |
a%b |
a.mod(b) |
a|b |
a.or(b) |
a&b |
a.and(b) |
a^b |
a.xor(b) |
a++ vagy ++a |
a.next() |
a-- vagy --a |
a.previous() |
a[b] |
a.getAt(b) |
a[b]=c |
a.putAt(b,c) |
a<
| a.leftShift(b) |
a>>b |
a.rightShift(b) |
switch(a){case(b):} |
b.isCase(a) |
~a |
a.bitwiseNegate(b) |
-a |
a.negative() |
+a |
a.positive() |
Megjegyzés: a most következő operátorok sosem dobnak NullPointerException-t.
Operator |
Method |
a==b |
a.equals(b) vagy a.compareTo(b)==0 * |
a!=b |
!a.equals(b) |
a<=>b |
a.comapreTo(b) |
a>b |
a.comapreTo(b)>0 |
a>=b |
a.comapreTo(b)>=0 |
a
| a.comapreTo(b)<0 |
a<=b |
a.comapreTo(b)<=0 |
* A bevezetőben már volt szó arról, hogy a == operátor sok esetben a java .equals()-al azonos. Az igazság az hogy ez nem minden esetben igaz, néha a == egyenlőnek mond két objektumot és az .equals() nem. A Groovy fejlesztői ígérik kijavítani ezt a hibát későbbi verziókban.
Lássunk egy példát operátor-felüldefiniálásra:
//a műveleteknek mindig páros szám a kimenetük.
//az eredményhez hozzáad 1-et ha ez nem így lenne
class OddNumber{
int value
OddNumber(int n){ value= (n%2)? n: n+1 }
def power(int n){ value**n }
def multiply(int n){ def i= value*n; (i%2)? i: i+1 }
def div(int n){ int i= value/n; (i%2)? i: i+1 }
def mod(int n){ int i= value - div(n)*n; (i%2)? i: i+1 }
def plus(int n){ int i= value + n; (i%2)? i: i+1 }
def minus(int n){ int i= value - n; (i%2)? i: i+1 }
def and(int n){ n == value }
def or(int n){ n == value || (n == value-1) }
def xor(int n) {n == value-1 }
def leftShift(int n){ value= (n%2)? n: n+1 }
def rightShift(int n){ (value * 10**n) + 1 }
def rightShiftUnsigned(int n){ (value * 10**(n*2)) + 1 }
def next(){ new OddNumber(value + 2) }
def previous(){ new OddNumber(value - 2) }
}
def e= new OddNumber(6)
assert e.value == 7
assert e**3 == 343 //calls power()
assert e*4 == 29 //calls multiply()
assert e/3 == 3 //calls div()
assert e%3 == -1 //calls mod()
assert e+5 == 13 //calls plus()
assert e-1 == 7 //calls minus()