A Groovy programozási nyelv

Nyelvi elemek

2. Nyelvi elemek

Azonosítók

Java-hoz képest eltérések:

  • '$' karakter nem használható
  • Sztring literálok
  • Sztring literálok egyszeres és kétszeres idézőjelekkel is definiálhatók:
    assert 'hello, world' == "hello, world" assert "Hello, Groovy's world" == 'Hello, Groovy\'s world' assert 'Say "Hello" to the world' == "Say \"Hello\" to the world"
  • Visszaperjellel karaktereket a kódjukkal is megadhatjuk:
    assert '\b' == '\010' //backspace assert '\t' == '\011' //horizontal tab assert '\n' == '\012' //linefeed assert '\f' == '\014' //form feed assert '\r' == '\015' //carriage return
  • Több soron átívelő sztringek definiálhatók háromszoros idézőjellel:
    assert '''hello, world''' == 'hello,\nworld' def text = """\ Good morning. Good night again."""
    vagy a hagyományos módon, ekkor minden sor végére kell egy visszaperjel:
    assert 'hello, \ world' == 'hello, world'
  • Megjegyzések
    Megjegyzéseket a következő módokon írhatunk a kódba:
  • '//'-től a sor végéig tart
    i++ //ez már a kommenthez tartozik /ez is//ez is
  • '/*'-tól a következő */-ig tart a megjegyzés, ez több soron is átívelhet. Nem lehet egymásba ágyazni kommenteket.
    /*ez már komment ez is /* ez is még , nem lehet egymásba ágyazni őket*/
  • Kulcsszavak

    Ugyan azok a kulcsszavak, mint Java-ban.
    +Az 'in' is kulcsszó.

    Változók deklarációja

    A kódban bárhol lehet változókat deklarálni. Ezt a def kulcsszóval tehetjük meg. Ilyenkor dinamikusan rendelődik típus a névhez, ami változhat új definíciónál.

    def a //a változó létrehozása; ekkor még nincs értéke def b = 1 //a változó deklarálva, és definiálva def a //fordítási hiba, a már definiált b = 'valami'//új típusú értéket kap; ez megengedett
    Megadhatjuk a változó statikus típusát is. Ha más típusú értéket próbálunk adni neki, akkor az érték automatikusan konvertálódik a változó típusára, ha ez nem lehetséges, akkor kivétel (GroovyCastException) váltódik ki.
    int i //integer i = 5 //i == 5 i = 'A' //i == 65, A automatikusan integer értékére konvertálódik

    Kifejezések

    Operátorok

    Groovyban a Java-val ellentétben az operátorok viselkedése testreszabható, megváltoztatható.

    Aritmetikai operátorok:
    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++ or ++a a.next()
    a-- or --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)
    switch(a) { case(b) : } b.isCase(a)
    ~a a.bitwiseNegate()
    -a a.negative()
    +a a.positive()

    Logikai operátorok:
    a == b a.equals(b) or a.compareTo(b) == 0
    a != b ! a.equals(b)
    a <=> b a.compareTo(b)
    a > b a.compareTo(b) > 0
    a >= b a.compareTo(b) >= 0
    a < b a.compareTo(b) < 0
    a <= b a.compareTo(b) <= 0
    Nem érzékenyek a null-okra, azaz ha az operandus null, a kiértékelés nem dob NullPointerException-t.

    def a = null def b = "foo" assert a != b assert b != a assert a == null
    Különböző típusú számok összehasonlításánál automatikusan a bővebb típusura konvertálódnak, ezért nem hibás a következő példa:
    Byte a = 12 Double b = 10 assert a instanceof Byte assert b instanceof Double assert a > b

    Gyűjtemények operátorai:
    'spread' operátor: segítségével egy összetett objektum minden elemén végrehajtható egy művelet.

    parent*.action //ugyan az, mintha minden elemre meghívnánk: parent.collect{ child -> child?.action }

    Objektumokhoz kapcsolódó operátorok:
    '.': (pont) operátor: tagfüggvény hívására, adattagok/propertyk elérésére használható.
    '.&': referencia operátor
    'as': típus kényszerítés
    '==': a java-s equals(), érték szerinti egyenlőséget vizsgál. Objektumok azonosságára használható az 'is' operátor.
    'instanceof': mint Java-ban. Megadja, hogy az objektum az adott típusú-e.

    További operátorok:
    'getAt()'/satAt()': iteratív adatszerkezeteknél adott pozíción lévő adat lekérése, beállítása. tartomány operátor '(..)' Elvis operator '?:': akkor használható, ha hamis vagy null értéknél szeretnék használható információt visszaadni pl.:

    def megjelenítendőNév = felhasznalo.neve ?: "Anonymous" //azaz ha neve adattag nem létezik, vagy false, akkor a '?:' után értéket adja vissza
    Safe Navigation Operator '?.': NullPointerException elkerülésére használható. Általában, ha van egy referencia egy objektumra, akkor ellenőrizni kell, hogy null-e, mielőtt bármilyen tagját(adat/metdus) megpróbálnánk elérni. Ez az operátor null-t ad vissza NullPointerException dobása helyett:
    def user = User.find( "admin" ) //null lehet, ha az 'admin' nem létezik def streetName = user?.address?.street //az utca neve null lesz, ha a user vagy user.address null, és ekkor nem dobódik kivétel

    Annotációk jelentése Groovyban

    A java kódban lehetséges annotálást használni, természetesen erre lehetőség nyílt Groovyban is. A következőkben pár annotáció kerül bemutatásra, olyan annotációk amlyek a groovyra jellemzőek.

    Típus ellenőrzés-@TypeChecked

    A Groovy 2.0 óta lehetőség nyílt típus ellenőrzésre. Ez egy opcionális lehetőség, amit a @TypeChecked annotációval érhetünk el. Hasonlóan mint Javaban itt is annotálhatjuk az adattagjainkat. Ha használjuk kódunkban az annotációt, akkor a fordító sokkal részletesebben közli a felmerült hibákat és kivételt is dobhat, olyan esetekben amikor például elgépeltünk valamit a kódban vagy olyan metódust próbálunk meghívni, ami nem található.
    Ez amiatt van, mert a Groovy egy dinamikus nyelv, futási időben értékeli ki a típusokat. Vannak olyan esetek amelyekben ez előny, például a következő kódrészletben:

    def builder = new MarkupBuilder(out) builder.html { head { ... } body { p 'Hello, world!' } }

    Az html tagek száma csak futási időben derül ki, ezért a metódusok még nem léteznek a fordítási időben, így egy típus ellenőrzés hátrányt jelentene ebben az ezetben a fejlesztő számára, hiszen korlátozva lenne egy ismeretlen html kód dinamikus feldolgozásában.

    Mikor használjuk a típus ellenőrzést és hogyan tegyük?

    Vannak esetek amikor szükségünk van ilyen megszorításra, ami biztonságosabbá teszi a kódunkat.A következő példában a foo metóduson típusát ellnőrzésre kerül a myextension.groovy fáljban található típus ellnőrzési szabályok alapján.

    @TypeChecked(extensions='/path/to/myextension.groovy') void foo() { ...}

    A DSL megengedi hogy megakaszuk a forditási folyamatot, sőt típus ellenőrzési kifejezéseket használjunk ’event-driven’ segítségével. Például a mikor a típus ellenörző belépe egy metódus törzsébe akkor kivált egy beforeVisitMethod eseményt, amelyre a következő képpen lehet reagálni a típus ellenőrző fájlban:

    beforeVisitMethod { methodNode -> println "Entering ${methodNode.name}" }

    A következő példában van egy robot objektumunk amit szeretnénk elöre mozgatni 100 métert.Ha például a Robot osztály kódja ez:

    robot.move 100
    class Robot { Robot move(int qt) { this } }

    akkor a szkriptünket a következő féleképpen tudjuk típus ellenőríztetni:

    def config = new CompilerConfiguration() config.addCompilationCustomizers(new ASTTransformationCustomizer(TypeChecked)) def shell = new GroovyShell(config) def robot = new Robot() shell.setVariable('robot', robot) shell.evaluate(script)

    Ebben az ezetben fordítási időben a robot változó nincs deklarálva hibát kapjuk. De ha a következő képpen átalakítjuk a kódot, akkor nem jön ebben az esetben hiba.

    config.addCompilationCustomizers(new ASTTransformationCustomizer(TypeChecked, extensions:['/path/to/myextension.groovy']))

    Ha a myextension.groovy tartalma a következő kód.

    unresolvedVariable { var -> if ('robot'==var.name) { storeType(var, classNodeFor(Robot)) handled = true } }

    A kód a robot változó esetén meghatározza a szkript, hogy az egy Robot típus.

    Események (event)
    1. setup: Az esemény akkor váltódik ki amikor elkezdődik a típus ellenőrzés.
    2. finish: Az esemény akkor váltódik ki miután befejeződött a típus ellenőrzés.
    3. unresolvedVariable: Az esemény akkor váltódik ki amikor a változóhoz nincs definició.
    4. unresolvedProperty: Az esemény akkor váltódik ki amikor a fogadó félnél nem található az beállítás mező amely hivatkozásra került.
    5. beforeMethodCall:Az esemény akkor váltódik ki amikor elkezdi a típus ellenőrzést a metódus törzsében.
    6. afterMethodCall: Az esemény akkor váltódik ki miután a kilépett a metódus törzséből.
    7. methodNotFound: Az esemény akkor váltódik ki ha a meghivott metódus nem található.
    8. beforeVisitClass: Az esemény azelött váltódik ki mielött belép a osztály törzsébe.
    9. afterVisitClass: Az esemény azután váltódik ki miután kilép az osztály törzséből.
    Meta-annotációk

    A meta annotációk olyan annotációk, amelyek fordítási időben más annotációkra cserélődnek ki. Egy meta annotáció kicserélődhet egy vagy akár több más annotációra is. A meta annotációk által jelentősen csökkenthetjük az olyan programkód méretét amelyben számos annotáció szerepel.
    Mikor és hogyan használjuk?

    Kezdjük egy egyszerű példával. Tegyük fel, hogy van két általunk definiált annotáció. Az egyiket úgy hívják, hogy @Transactional a másikat pedig úgy, hogy @Service. Szeretnénk az osztályunkat ellátni mindkét annotációval. Ezt a következőképpen tehetjük meg:

    @Service @Transactional class MyTransactionalService {}

    A meta annotáció segítségével ezt a két annotációt kicserélhetjük egyetlen annotációra. Ezt a következőképpen tehetjük meg: először is egy aliast kell definiálnunk a következő módon:

    import groovy.transform.AnnotationCollector ?? @Service @Transactional @AnnotationCollector public @interface TransactionalService { }

    A létrehozott @TransactionalService annotáció egy meta annotáció, amely egybefoglalja számunkra a két annotációnkat. Ezt a működést úgy érjük el, hogy az interfészt annotáljuk @AnnotationCollector-ral.

    Működés
    Mielőtt továbbmegyünk, néhány szó a működésről: először is, a Groovy-ban lehetőség van használni már lefordított, vagy a forrásban jelen levő meta annotációkat is.
    Tehát a használt meta annotáció lehet korábban lefordított (máshonnan importált), vagy az adott forrásban definiált. Fontos ezenkívül, hogy a meta annotáció egy kizárólag a Groovy-ban használható konstrukció. Ez azt jelenti, hogy egy Java osztályhoz nem lehet (egy Groovy-ban megírt) meta annotációt használni, és ugyanígy Java-ban nem lehet meta annotációt írni - mind a meta annotáció definíciója és a felhasználása Groovy-ban kell, hogy történjen.
    Amikor a Groovy fordító egy meta annotációt talál, akkor a programkódban kicseréli azt az általa egybefoglalt annotációkkal. Tehát az előző példánkat véve: a fordító a @TransactionalService meta annotáció helyére a @Transactional és a @Service annotációkat illeszti. Ez a lépés a szemantikus analízis során történik.

    Meta annotáció paraméterek
    Az annotációk egybefogásán túl a meta annotációk használhatók a tartalmazott annotációkkal kapcsolatos műveletekre is, ideértve a hozzájuk tartozó paraméterek feldolgozását.

    A következő példában két annotációnk van, mindkettő egy-egy paraméterrel:

    @Timeout(after=100) @Dangerous(type="explosive")

    Ezeket szeretnénk egy meta annotációval helyettesíteni, @Explosive néven:

    @Timeout(after=100) @Dangerous(type="explosive") @AnnotationCollector public @interface Explosive {}

    Ezután amikor az @Explosive-ot a korábbi két annotáció helyett használjuk, az azokban definiált értékeket fogja a meta annotáció is tartalmazni.
    Azonban ezek az értékek felülírhatók

    @Explosive(after=0) class Bomb {}

    Ekkor a megadott értékkel felülírjuk a korábban a @Timeout-ban definiált értéket.

    Névütközések
    Névütközésről ebben az esetben akkor van szó, ha két vagy több annotációban ugyanazzal a névvel definiálunk paramétert. Ebben az esetben, ha az őket összefogó meta annotációban értéket adunk ennek a "közös" paraméternek, akkor az az érték az összes lehetséges helyre bemásolódik, felülírva mindegyiket.
    Például a következő kódrészlet:

    import java.lang.annotation.Retention import java.lang.annotation.RetentionPolicy @Retention(RetentionPolicy.RUNTIME) public @interface Foo { String value() } @Retention(RetentionPolicy.RUNTIME) public @interface Bar { String value() } @Foo @Bar @groovy.transform.AnnotationCollector public @interface FooBar {} @Foo('a') @Bar('b') class Bob {} println Bob.class.getAnnotation(Foo) println Bob.class.getAnnotation(Bar) @FooBar('a') class Joe {} println Joe.class.getAnnotation(Foo) println Joe.class.getAnnotation(Bar)

    a következő eredményt adná:

    @Foo(value=a) @Bar(value=b) @Foo(value=a) @Bar(value=a)

    Mint láthatjuk, a második esetben (amikor meta annotációt használtunk, annak paramétert adva) a megadott érték mindkét annotációba bemásolódott.
    Abban az esetben, ha ugyan a paraméterek neve ugyanaz, de a típusuk különböző, fordítási hibát kapunk. Azonban ennek a problémának a kiküszöbölésére lehetőség van az úgynevezett feldolgozót használni.
    Saját annotáció feldolgozók
    A saját annotáció feldolgozó segítségével a programozó tetszőlegesen befolyásolni tudja az annotáció működését. Példaként nézzük meg, hogyan van a @CompileDynamic implementálva a Groovy 2.1.0-ban. A @CompileDynamic egy meta annotáció, amely kicserélődik @CompileStatic(TypeCheckingMode.SKIP)-re. A meta annotáció feldolgozó azonban nem tartalmaz enum-okat, ezért azt nekünk kell előállítanunk.
    Ezért ahelyett, hogy ezt írnánk:

    @CompileStatic(TypeCheckingMode.SKIP) @AnnotationCollector public @interface CompileDynamic {}

    A meta annotációt inkább így fogjuk definiálni:

    @AnnotationCollector(processor = "org.codehaus.groovy.transform.CompileDynamicProcessor") public @interface CompileDynamic { }

    A legszembetűnőbb változás, hogy az interfészünk már nincs a @CompileStatic-kal annotálva. Ehelyett a 'processor' paramétert használjuk. Ez egy olyan osztályra hivatkozik, amely legenerálja az annotációt.
    Ez az osztály a következőképpen néz ki

    public class CompileDynamicProcessor extends AnnotationCollectorTransform { private static final ClassNode COMPILESTATIC_NODE = ClassHelper.make(CompileStatic.class); private static final ClassNode TYPECHECKINGMODE_NODE = ClassHelper.make(TypeCheckingMode.class); public List visit(AnnotationNode collector, AnnotationNode aliasAnnotationUsage, AnnotatedNode aliasAnnotated, SourceUnit source) { AnnotationNode node = new AnnotationNode(COMPILESTATIC_NODE); node.addMember("value", new PropertyExpression(new ClassExpression(TYPECHECKINGMODE_NODE), "SKIP")); return Collections.singletonList(node); } }

    Ez a saját feldolgozó az alapértelmezett feldolgozóból származik és felüldefiniálja a 'visit' metódust. Ez a metódus azon annotációk listájával tér vissza, amelyek az absztrakt szintaxisfában (AST) a meta annotáció helyére fognak kerülni. Tehát a metódus feladata mindössze annyi, hogy legenerálja a megfelelő AnnotationNode elemet. Természetesen lehetséges az ősosztályra is támaszkodni a működés során, például a paraméterek feldolgozásában.