k47.cz
mastodon twitter RSS
bandcamp explorer

Groovy

1. 8. 2011 (před 11 lety) — k47 (CC by-nc-sa)

Groovy je skriptovací jazyk, který běží v JVM, vychází z Javy a dokáže využívat všechny knihovny v ní napsané. Oproti staticky typované Javě je typovaný dynamicky (podporuje tedy duck typing). Navíc do samotného jazyka přidává několik vylepšení inspirovaných Pythonem, Ruby nebo Smalltalkem, které mohou zásadně zvýšit produktivitu psaní kódu za cenu vyšší runtime režie. Výkon výsledné aplikace je mnohem menší než ekvivalent napsaný v čisté Javě, což je způsobeno zmíněným dynamickým typováním, které vnitřně hojně využívá reflexi. Přesto může být použití Groovy výhodné. Když vezmeme v potaz kratší čas vývoje a možnost používat všechny Javovské knihovny, může se z Groovy stát „lepidlo“, které rychle pospojuje několik komponent.


Uzávěry

Největším tahákem Groovy jsou uzávěry neboli closures (těch se možná, ale opravdu jenom možná doškáme v Javě 7). Closure je něco jako anonymní funkce, která ovšem může přistupovat k proměnným definovaným v kontextu, jež ji obklopuje. Nemusí tedy s okolním světem komunikovat jenom přes parametry a nechová se tedy zcela jako anonymní funkce, ale spíš jako řídící struktura nebo blok kódu. Díky tomu se dá pomocí uzávěrů snadno udělat to, co by se jenom velice těžko obcházelo bez nich (například použitím anonymních tříd známých z Javy). Uzávěry by nebyly ani zdaleka tak užitečné, kdyby nebyly k mnoha objektům ze standardní Javovské knihovny (především ke kolekcím) přidány nové metody, které uzávěry využívají.

Všechno osvětlí malý příklad z Wikipedie.

Javovský kód:

for (String it : new String [] {"Rod", "Carlos", "Chris"}) {
  if (it.length() <= 4) {
     System.out.println(it);
  }
}

ekvivalentní Groovy kód:

["Rod", "Carlos", "Chris"].findAll{ it.size() <= 4 }.each{ println it }

Metoda findAll vrátí kolekci elementů, které odpovídají podmínce z closure. Když nejsou uvedeny parametry explicitně, použije se implicitní proměnná it. Explicitní forma closure by vypadala takto: { elem -> elem.size() <= 4 }. Navíc metoda (pokud není deklarována jako void) automaticky vrací výsledek posledně vyhodnoceného výrazu. Proto si můžeme ušetřit mnoho zápisů return a také proto se uvedená closure vyhodnotí, jak bychom předpokládali. Metoda each pak iteruje nad vrácenou kolekcí. Díky tomu (a dalším novinkám, které zmíním později), prakticky odpadá nutnost psát klasické for/foreach/while cykly.

Z ukázky je pak patrná další věc a tou jsou nové literály pro kolekce. V Groovy se ArrayList vytvoří velice snadno:

def list = [1, 2, 3, "string", null, new Object()]

Asociativní pole se vytvoří také velice jednoduše:

def map = [ klic1: "Anomalocaris Detrimentum", klic2: "2084", klic3: "Cerv" ]

Klíče jsou automaticky považovány za řetězce i když kolem sebe nemají žádné uvozovky nebo apostrofy. Když chceme, aby byl klíč vyhodnocen jako proměnná, jednoduše kolem něj dáme závorky (je to sice nekonzistentní, ale na druhou stranu to reflektuje skutečnost, že v 99% případů chceme, aby klíčem byl řetězec).

operátory

Groovy do jazyka přidává některé užitečné nové operátory, které o trochu zjednoduší život.

?. Safe Navigation Operator
– je ekvivalentní s normálním tečkovým operátorem pro přístup k metodám nebo proměnným, ale když je pomocí něho volána metoda na nulové referenci, zamezí vyhození výjmky NullPointerException, ale pouze vrátí null
– s jeho pomocí je možné bezpečně volat něco takového a nestarat se nulové reference

user?.address?.steet

*. Spread Operator
– Toto je velice užitečný operátor, který nad každým prvkem kolekce provede danou operaci (volání metody, přístup k property) a výsledek vrátí v kolekci. Jde o další způsob, jak se vyhnout psaní cyklů.

parent*.action                  //je ekvivalentní s
parent.collect{ it?.action }

def names = persons*.name       //vrátí kolekci jmen

Když to porovnáme s nejúspornějším řešením v Javě:

ArrayList names = new ArrayList();
for (Person p : persons) {
	names.add(p.getName())
}

je jasně vidět, co je stručnější.

persons*.address*.city          //lze provést i vícenásobně, protože se vždycky vrátí objekt List
myObjects*.toString()           //na každém objketu zavolá toString() a vrátí kolekci

?: elvis operátor
– jde o kompaktnější verzí ternárního operátoru a funguje tak, že když je výraz před Elvisem vyhodnocen jako true, pak se vrátí samotný výraz, když jako false vrátí se to, co se nachází za Elvisem

def whoIsElvis = elvis ?: defrauder

//ekvivalentní s

def whoIsElvis = elvis ? elvis : defrauder

Regulární výrazy

Regulární výrazy byly začleněny do jazyka na syntaktické úrovni pomocí tří nových operátorů:

~string             // vytvoří Pattern ze stringu
string =~ pattern	  //hledá vzor ve stringu (find)
string ==~ pattern  //hledá přesnou shodu (match)

Ve spojení s uzávěrami je práce s regexy ještě snadnější a přehlednější:

def dates = []
def line = "Today is 31.01.2007, 15:01. Tomorrow is 01.02.2007, 15:01"

(line =~ /(\d\d.\d\d.\d\d\d\d), (\d\d:\d\d)/).each { all, date, time ->
	//každý výskyt regulárního výrazu je předán jako parametr pro tuto closure
    dates << date
}

Rozsahy

Rozsahy jsou další užitečnou vlastností Groovy. Jednoduše reprezentují rozsah hodnot od A do B.

def range1 = 2..10   //rozsah 2 až 10
def range2 = 2..<10  //rozsah 2 až 9

Rozsahy se dají použít k vytvoření podřetězců string[1..4] nebo stejným způsobem k vytvoření podmnožin ArrayListu. Rozsah můžeme použít ve switchi:

switch (someValue) {
    case 1..3 : feedback = 'quite ok'; break
    case 3..6 : feedback = 'needs improvement'; break
    default: feedback = 'unknown'
}

nebo v cyklu

for (i in range) {
    out << i
}

Cykly

A tím se dostáváme k cyklům, které jsou silně inspirovány Ruby.

Cyklus můžete vytvořit úplně jednoduše:

8.times {
	print "průchod číslo ${it+1}"
}

Nebo pomocí rozsahu:

(4..8).each {
	println it
}

Možností je hodně a jejich kód je velice samo-popisný. Groovy dokonce klasické for-cykly nepodporuje, ale jak je vidět, nejsou třeba.

Objekty

Na rozdíl od Javy, která má primitivní typy, je v Groovy všechno objekt. Když k tomu připočteme, že použití operátoru je jenom maskované volání funkce, dostáváme možnost přetěžovat operátory. To využívá jmennou konvenci (operátor + volá metodu plus(), – volá minus(), << volá leftShift() atd).

Všechny operátory jsou hojně využívány ve standardní knihovně, která je rozšířena o mnoho metod.

Např: << standardně provede připojení elementu do kolekce:

collection = []
collection << "elem"
collection << 1

Dále se jako false vyhodnocují obecně všechny objekty, které jsou považovány za prázdné, tedy prázdné kolekce, prázdné stringy, null a číslo 0.

Property

Dalším syntaktickým cukrem jsou property. Jde o zjednodušený způsob volání getterů a setterů již existujících javovských tříd.

def x = obj.someting   // ekvivalentní s obj.getSomething()
obj.someting = x       // ekvivalentní s obj.setSomething(x)
píše k47, ascii@k47.cz