05 czerwca 2009

Dokończenie relacji z rozdziału 3. "Groovy for the Java eyes" z "Programming Groovy"

Skończyłem moją ostatnią relację z lektury "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama na stronie 32. rozdziału 3. "Groovy for the Java eyes" (zajrzyj do Rozpoczynam lekturę "Programming Groovy: Dynamic Productivity for the Java Developer" Venkata Subramaniama).

Jeśli ktoś pamięta z C++ przeciążanie operatorów, to w Groovy też to jest. Wykonanie jakiegokolwiek operatora sprowadza się w Groovy do wykonania odpowiadającej mu metody. Nie ma jej, wykonana zostanie domyślna. Lista operatorów w Groovy znajduje się na stronie Operator Overloading. Chcemy skrótowo wykonywać pewne metody, mamy mnemoniki w postaci symboli matematycznych, ale możemy również użyć metod bezpośrednio. Zaintrygowany? Uruchom groovyConsole, przeklej poniższy skrypt i uruchom przez Ctrl+R.
 class Pracownik {
String imie
int zarobki

def next() {
println 'Wywołano next()'
zarobki *= 2
this
}
}

p = new Pracownik(imie: 'Jacek', zarobki: 50)

p++ // awansuje

// sprawdźmy jego zarobki po awansie
p.zarobki
Nie spodziewałem się, że może mi się jeszcze spodobać ten powrót do korzeni. Najpierw "prawda Groovy", a teraz przeciążanie operatorów. Bajka! Ciekawie przedstawia się możliwość iterowania przez alfabet w pętli for.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
----------------------------------------------
groovy:000> for (i in 'a'..'c') { println i }
a
b
c
===> null
Dodanie elementu do kolekcji to << (leftShift()), np.
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
------------------------------------------------------------------------------------
groovy:000> polskieKonferencjeJavowe = ['GeeCON', 'java4people']
===> [GeeCON, java4people]
groovy:000> polskieKonferencjeJavowe << 'Javarsovia'
===> [GeeCON, java4people, Javarsovia]
groovy:000> polskieKonferencjeJavowe << 'Warsjava'
===> [GeeCON, java4people, Javarsovia, Warsjava]
groovy:000> polskieKonferencjeJavowe.size()
===> 4
groovy:000> polskieKonferencjeJavowe.each { konferencja -> println "$konferencja" }
GeeCON
java4people
Javarsovia
Warsjava
===> [GeeCON, java4people, Javarsovia, Warsjava]
W Groovy wszystko jest obiektem. Możemy wciąż korzystać z typów prostych (prymitywnych), ale wewnętrznie będzie to jego odpowiednik obiektowy, np. int -> java.lang.Integer. Jeśli jednak wywołamy metodę w Javie, która przyjmuje typ prosty, Groovy zadba o właściwe przekazanie (właściwie odpakowanie).
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
----------------------------------------------------------------
groovy:000> val = 5
===> 5
groovy:000> val.class
===> class java.lang.Integer
groovysh nie akceptuje definicji typów czy def, więc musiałem wykonać inicjalizację val = 5, jednakże w groovyConsole wykonanie
 int val = 5
val.class
zwróci Result: class java.lang.Integer.

Pętla foreach w Javie wymaga określenia typu i tak też jest w Groovy. Możemy jednak skorzystać z jeszcze bardziej uproszczonej pętli foreach z in zamiast javowego : (dwukropek).
 $ groovysh
Groovy Shell (1.6.3, JVM: 1.6.0_14)
Type 'help' or '\h' for help.
--------------------------------------------------------------------------------
groovy:000> konferencjePL = ['GeeCON', 'java4people', 'Javarsovia', 'Warsjava']
===> [GeeCON, java4people, Javarsovia, Warsjava]
groovy:000> for (String konferencja: konferencjePL) {
groovy:001> println konferencja
groovy:002> }
GeeCON
java4people
Javarsovia
Warsjava
===> null
groovy:000> for (konferencja in konferencjePL) {
groovy:001> println konferencja
groovy:002> }
GeeCON
java4people
Javarsovia
Warsjava
===> null
Groovy pozwala na podanie zakresu typów wyliczeniowych w case w wyrażeniu switch przez podanie akceptowanych wartości w postaci listy lub operatora zakresu .. (wielokropek).
 import static Lang.*

enum Lang { EN_UK, EN_US, PL, GE }

def przywitajSie(lang) {
switch (lang) {
// również case [EN_UK, EN_US]:
case EN_UK..EN_US:
println "Howdy!"
break
case PL:
println "Cześć!"
break
case GE:
println "Moin moin!"
break
}
}

przywitajSie(EN_UK)
przywitajSie(PL)
Autor wspomina o częściowym wsparciu dla adnotacji w Groovy (strona 52) - użycie możliwe, ale tworzenie nie. Na stronie dokumentacji Annotations with Groovy (u dołu) napisano, że "Future work may include allowing you to write the annotation definitions themselves in Groovy and also potentially may use annotations within the Groovy language itself or within accompanying tools." Może ktoś już sprawdził i tworzył adnotacje w Groovy? Wspierane?

Kompilator Groovy groovyc ignoruje adnotacje @Deprecated oraz @Override i produkuje bajtkod bez informacji o nich.

Niezwykle ciekawym poglądem autor podzielił się z czytelnikami na temat statycznych importów (strona 53):

"Static import in Java improves job security. If you define several static imports or use * to import all static methods of a class, you're sure to confuse the heck out of programmers trying to figure out where these methods come from".

Interesujący pogląd, szczególnie w kontekście wszystkich tych cudów Groovy jak przeciążanie operatorów, prawa Groovy czy same domknięcia, a o MOPie nie będę wcale wspominał. To dopiero "job security"!

Groovy wspiera aliasowanie importów metod i klas, statycznych i tych niestatycznych, przez słowo kluczowe as, np.
 import static Math.random as rand
import groovy.lang.ExpandoMetaClass as EMC

double value = rand()
def metaClass = new EMC(Integer)
Uwaga na brak silnego typowania w Groovy w kontekście typów generycznych. To, co nie do zaakceptowania przez statyczną kontrolę kompilatora Java jest jak najbardziej akceptowalne dla kompilatora Groovy. Jeśli tylko nie "dotkniemy" kodu, w którym wykonujemy coś nielegalnego w Javie, Groovy pozwoli naszemu programowi działać i to bez zgłoszenia jakiegokolwiek wyjątku czy komunikatu. Kolejne "job security".

Na zakończenie rozdziału autor przedstawia serię "gotchas", czyli tego, co może nas zaskoczyć przy pracy z Groovy i może nie być miłe. Weźmy takie == i equals() z Javy. Z tym jest nie lada problem, aby to zrozumieć, a Groovy sprawił, że to jest pikuś, bo mamy teraz is(). Pamiętamy o przeciążaniu operatorów w Groovy? == jest równe wywołaniu equals() w Groovy, więc jeśli faktycznie chcemy wykonać porównanie referencji musimy wykonać is(). Proste? Nie?! No cóż, nie wszystko musi takie być :)

Kolejna sprawa to opcjonalny return. Możemy go opuszczać do woli, ale przy if musimy uważać. Jeśli ostatnim wyrażeniem był if Groovy zwróci null. Opcjonalne typowanie też może przyspożyć nam kłopotu, kiedy przypiszemy zmiennej typu liczbowego wartość znakową. groovyc skompiluje program bez jakiegokolwiek komunikatu, ale podczas wykonania, kiedy to JVM dotknie naszego bajtkodu pojawi się wyjątek rzutowania. Właśnie! Wyjątek pojawi się podczas uruchomienia, kiedy dotrzemy do tego miejsca - wcześniej możemy przecież wykonać różne wygibasy MOPowe w Groovy, które ostatecznie sprawią, że to, co mogło być niedozwolone podczas kompilacji, już takim nie będzie podczas uruchomienia (uwaga na podwójne zaprzeczenie :)). Autor przedstawia wynik działania dekompilatora Javy - javap na potwierdzenie swoich słów, więc żądni wrażeń proszeni są do książki.

Należy uważać na nowe słowa kluczowe w Groovy - def, in czy as. Jeśli integrujemy się z istniejącym kodem w Javie, które korzysta z tych słów jako nazw zmiennych lub metod możemy mieć kłopoty. Użycie it też może zaboleć, kiedy klasa posiada pole o takiej nazwie. W zasięgu domknięcia na scenę wchodzi przesłanianie zmiennych i it będzie wskazywało na parametr wejściowy zamiast pole.

Groovy nie wspiera wewnętrznych bloków w metodach, które w Javie tworzą nowy zasięg widoczności zmiennych. Ta konstrukcja zarezerwowana jest dla domknięć.

Opcjonalny średnik też może przyspożyć problemów, kiedy zabraknie go między deklaracją pola instancji (właściwości), a blokiem inicjalizacyjnym instancji, np.
 class Semi
{
def val = 3

{
println "Instance Initializer called..."
}
}
Brak średnika sprawia, że Groovy traktuje blok inicjalizacyjny jako domknięcie sądząc, że chcemy przekazać je jako...parametr wejściowy dla 3 (!) Wystarczy dodać średnik po 3 i będzie cacy.

Ostatnim dziwolągiem Groovy jest "zniszczenie" sposobu konstruowania tablicy typu
 int[] arr = new int[] {1, 2, 3, 4, 5}
W Groovy to nie przejdzie. Musimy skorzystać z prostszej konstrukcji
 int[] arr = [1, 2, 3, 4, 5]
, co i tak pewnie byśmy robili, nieprawdaż?