2012. június 27., szerda

Barátkozás a Groovyval

Régóta terveztem, hogy megismerkedek a Groovy rejtelmeivel, és most úgy alakult, hogy egy hosszan tartó barátság első napjait élem. A nyelv ismertetést nem is az alapoknál kezdeném, hiszen azok egy Java fejlesztőnek nem szabad, hogy gondot okozzanak, mivel visszafelé teljes a kompatibilitás. Sokkal inkább koncentrálnék a kedvenc programozói eszközökre, amikkel új dimenziókba lehet helyezni az eddig megszokott Java programozást.

Van két fontos különbség a Java és a Groovy között, amit mindenképpen megosztanék ezen a ponton. Groovyban nincsenek primitív típusok, még ha látszólag úgy is deklarálunk egy változót, az eredmény mindig egy példány lesz a memóriában. A másik, hogy a dupla egyenlőség vizsgálat, Javatól eltérően érték szerint hasonlít össze!

Minden Java fejlesztő rémálma a NullPointerException, éppen ezért Javaban a műveletek nagy részét megelőzi erre vonatkozó ellenőrzés, ami csak átláthatatlanabbá teszik a kódot. Groovyban ez a teljes vizsgálat elvégezhető egyetlen kérdőjel segítségével:

int foo = bar?.length ? bar.length : -1;
Az eredmény, sokkal tömörebb kód, miközben az olvashatóságot sem rontja a szintaxis. A történet egyszerű. A kérdőjel helyére egy null ellenőrzést ékel a fordító.

Következő hasznos egyszerűsítés amit meg szeretnék említeni az un. Elvis operátort.
int foo = bar ?: -1;
Az Elvis operátorral az alapértelmezett értéket lehet meghatározni, amennyiben az eredeti "érték" false vagy null.

Soron következő kedvencem a GString. Stringek összefűzésének problémájával a legtöbben már egészen biztos találkoztunk. Kis mennyiségű szöveg összefűzésénél még nem is akkora a probléma, mert az egy sorban elvégzett String összefűzés automatikusan egy StringBulder osztályra fordul. Nagyobb mennyiség esetén (hallottam olyan helyről, ahol a mai napig 80 karakter sorhossz) macerássá válik a művelet. A Groovy eszköztárában egy az Expression Language-re kísértetiesen hasonlító megoldást építettek.
String foo = "Foo"
String bar = "${foo} Bar" 
Ezt a funkcionalitást kombinálva a több soros stringek deklarációjával, máris kézzelfogható előnyhöz jutunk:
def sql = """
select * from ${table}
where bar = ${foo}
"""
Fontos tudni, hogy a szimpla idézőjelek között létrehozott 'stringek' hagyományos java.lang.String példányok lesznek, a duplával pedig GStringek, ezért ha nem szeretnénk a GString sajátosságait kihasználni, mindig szimpla idézőjellel példányosítsuk stringjeinket.

Reguláris kifejezések használatát is lényegesen leegyszerűsítették a Groovys srácok.
Pattern pattern = ~/(.*)/
boolean find = 'foo' ==~ pattern
Matcher m = 'foo' =~ pattern

Mint ahogy a bevezetőben említettem, Groovyban nincsenek primitív típusok, most lássuk, hogy ennek miért is van jelentősége. A fordító bizonyos operátorokat automatikusan átfordítja az objektum metódus hívásaira.
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++ o r ++a // a.next()
a−− o r −−a // a.previous()
a [ b ] // a.getAt(b)
a [ b ] = c // a.putAt(b, c)
a << b // a.leftShift(b)
a >> b // a.rightShift(b)
~a // a.bitwiseNegate()
−a // a.negative()
+a // a.positive()
a <=> b : a.compareTo(b)
Ennek előnye egyrészt, hogy megkíméli a programozót rengeted felesleges gépeléstől, másrészt ezt a működést kihasználva saját osztályainkat is fel tudjuk készíteni, hogy értsék a különböző operátorokat. A Groovyban van is erre jó példa, pl. a Date osztályban.
def today = new Date()
def tomorrow = today + 1
def yesterday = today - 1
assert today.plus(1) == tomorrow
assert tomorrow.minus(1) == today
Fontos megértenünk 2 dolgot az operátorok átfordítása kapcsán. Az egyik, hogy vannak esetek, amikor a visszatérési objektum tipusa más lesz, mint a operandusé.
StringBuilder sb = 'bar' << 'foo'
A másik dolog, a túlcsordulást elhárító típusbővítés, ami azt jelenti például, hogy az 1 + 1.5 az ((BigInteger) 1.5).plus(1) -ra fordul, és az eredmény egy BigDecimal osztályban kerül tárolásra, hiába az Integer állt előbb. A Groovy decimális számok tárolására alapértelmezetten a BigDecimalt használja, elkerülendő a lebegőpontos számok ábrázolásából fakadó hibákat.

A következő érdekesség amire szeretném felhívni a figyelmet a Groovy osztálykezelése. A Groovy egy speciális osztályon keresztűl hozzáférést biztosít az osztályokhoz, és lehetőséget ad azok bővítésére.
String.metaClass.prefixFirstLette = { prefix ->
    return "${prefix}_${delegate.substring(0, 1)}"
}
println 'bar'.prefixFirstLette('foo');

Az előző példában egy újabb speciális Groovy osztállyal találkozhattunk, a Closure-val, mely osztály kiemelten fontos a nyelv szempontjából, és számtalan metódusnak átadható paraméterként.
Closure c = { i ->
    return i
}
println c.call(1)
A Closure segítségével a Groovy szimulálni tudja a Javaból egyébként igencsak hiányzó névtelen függvények használatát.

Következő témakör, amelyet fontos kihangsúlyozni a Groovyval kapcsolatban, hogy natív támogatást nyújt listák és mapok kezelésére, ráadásul számos olyan funkcióval egészítették ki ezen osztályokat, amik megkönnyítik a velük végzett műveleteket. Pár példa a teljesség igénye nélkül:
def words = ['ant', 'buffalo', 'cat', 'dinosaur']
assert words.findAll{ w -> w.size() > 4 } == ['buffalo', 'dinosaur']
assert words.collect{ it[0] } == ['a', 'b', 'c', 'd']

def list = [[1,0], [0,1,2]].sort { item -> item.size() }
assert list == [ [1,0], [0,1,2] ]

assert [1, 3, 5] == ['a', 'few', 'words']*.size() //minden elemen végrehajtja a size() metódust
A GDK plusz extraként kiegészít minden tömböt, kollekciót, és Stringet egy további toList() metódussal.
def greeting = 'Hello Groovy!'
assert greeting[6..11] == 'Groovy'
assert greeting[0,2,4] == 'Hlo'

A következő érdekesség az XML kezelés Groovyban. Okulva a Java hiányosságából, szintén natív támogatás van XML struktúrák kezelésére.
def builder = new groovy.xml.MarkupBuilder()
builder.book {
    author 'Bar Foo'
    title 'sometitle'
    properties {
        pages 42
    }
}
println builder

  Bar Foo
  sometitle
  
    42
  

A Streamek kezelésében is hoz változást a Groovy. Javaval ellentétben nem kell ciklust írnunk a tartalom áttöltéséhez.
def address = 'http://jpattern.blogspot.com/favicon.ico'
def file = new FileOutputStream(address.tokenize("/")[-1])
def out = new BufferedOutputStream(file)
out << new URL(address).openStream()
out.close()

Utoljára hagytam a legkevésbé fontos, de talán mégis hasznos újítást az importok területén. Lehetőség van Groovyban importált osztály-t aliasszal megjelelölni.
import org.springframework.context.i18n.LocaleContextHolder as LCH
...
def locale = LCH.getLocale()

A pozitívumok után következzenek a negatívumok, bár személy szerint nem sok ilyet találtam. Az első, hogy a Groovy nem támogatja belső osztályok definiálását, ami szerintem a Java eszköztárának egy fontos kelléke. A Másik, hogy dinamikus típusú nyelv lévén az IDE támogatás meg sem közelíti a Javaét. Bár mindhárom elterjedt IDE (Netbeans, Eclipse, IntelliJ) rendelkezik Groovy támogatással, Javahoz szokott fejlesztőként számtalan kényelmi funkciót kell nélkülözni.
Véleményem szerint egy elég erőteljes nyelv lett a Groovy, a fejlesztők igyekeztek a Java hiányosságaiból tanulni, miközben megőrizték a Javaban rejlő erőt teljes mértékben. Bár mindenki azt csinál amit akar, én személy szerint alapos Java ismeretek nélkül nem ajánlom a nyelvet kezdőknek, ugyanis ahhoz elég sok dologban tér el a Javatól, hogy rossz szokásokat fejlesszen későbbi Java programozáshoz. Ilyen pl. a dupla egyenlőség vizsgálat, a String automatikus StringBuilderré alakítása bizonyos operátorok használatakor, A streamek kezelése, stb. Remélem további ismerkedésre inspirál mindenkit ez a kis írás, és sokan kiegészítik ezzel a remek eszközzel programozói repertoárjukat.