The document discusses the key features that make Groovy a popular programming language. It highlights how Groovy simplifies development for Java developers by being a superset of Java with additional features. Groovy code is as safe and fast as Java due to static typing and compilation to Java bytecode. Groovy also offers an expressive, concise and readable syntax.
23. Speaking of conciseness...
A full Spring app in the span of a tweet!
@RestController
class
App
{
@RequestMapping("/")
String
home()
{
"Hello
World!"
}
}
62. Optional...
class
Greeter
{
String
owner
!
!
!
!
!
!
!
!
!
String
greet(String
name)
{
"Hello
"
+
name
+
",
I
am
"
+
owner
}
}
!
def
greeter
=
new
Greeter()
greeter.setOwner
"Guillaume"
!
println
greeter.greet("Marion")
!28
63. Optional...
class
Greeter
{
String
owner
!
!
!
!
!
!
!
!
!
String
greet(String
name)
{
"Hello
"
+
name
+
",
I
am
"
+
owner
}
}
!
def
greeter
=
new
Greeter()
greeter.setOwner
"Guillaume"
!
println
greeter.greet("Marion")
Property notation
!28
64. Optional...
class
Greeter
{
String
owner
!
!
!
!
!
!
!
!
!
String
greet(String
name)
{
"Hello
"
+
name
+
",
I
am
"
+
owner
}
}
!
def
greeter
=
new
Greeter()
greeter.owner
"Guillaume"
!
println
greeter.greet("Marion")
!29
65. Optional...
class
Greeter
{
String
owner
!
!
!
!
!
!
!
!
!
String
greet(String
name)
{
"Hello
"
+
name
+
",
I
am
"
+
owner
}
}
!
def
greeter
=
new
Greeter()
greeter.owner
"Guillaume"
!
println
greeter.greet("Marion")
Named argument
constructor
!29
66. Optional...
class
Greeter
{
String
owner
!
!
!
!
!
!
!
!
!
String
greet(String
name)
{
"Hello
"
+
name
+
",
I
am
"
+
owner
}
}
!
def
greeter
=
new
Greeter(owner:
"Guillaume")
!
!
println
greeter.greet("Marion")
!30
67. Optional...
class
Greeter
{
String
owner
!
!
!
!
!
!
!
!
!
String
greet(String
name)
{
"Hello
"
+
name
+
",
I
am
"
+
owner
}
}
!
def
greeter
=
new
Greeter(owner:
"Guillaume")
!
!
println
greeter.greet("Marion")
Interpolated strings!
(aka GStrings)
!30
68. Optional...
class
Greeter
{
String
owner
!
!
!
!
!
!
!
!
!
String
greet(String
name)
{
"Hello
${name},
I
am
${owner}"
}
}
!
def
greeter
=
new
Greeter(owner:
"Guillaume")
!
!
println
greeter.greet("Marion")
!31
69. Optional...
class
Greeter
{
String
owner
!
!
!
!
!
!
!
!
!
String
greet(String
name)
{
"Hello
${name},
I
am
${owner}"
}
}
!
def
greeter
=
new
Greeter(owner:
"Guillaume")
!
!
println
greeter.greet("Marion")
Let’s reformat that
mess of whitespace!
!31
70. Optional...
class
Greeter
{
String
owner
!
String
greet(String
name)
{
"Hello
${name},
I
am
${owner}"
}
}
!
def
greeter
=
new
Greeter(owner:
"Guillaume")
!
println
greeter.greet("Marion")
!32
71. Optional...
public
class
Greeter
{
private
String
owner;
!
public
String
getOwner()
{
class
Greeter
{
return
owner;
String
owner
}
!
!
String
greet(String
name)
{
public
void
setOwner(String
owner)
{
"Hello
${name},
I
am
${owner}"
this.owner
=
owner;
}
}
}
!
!
public
String
greet(String
name)
{
def
greeter
=
new
Greeter(owner:
"Guillaume")
return
"Hello
"
+
name
+
",
I
am
"
+
owner;
!
}
println
greeter.greet("Marion")
}
!
Greeter
greeter
=
new
Greeter();
greeter.setOwner("Guillaume");
!
System.out.println(greeter.greet("Marion"));
!32
72. Optional...
class
Greeter
{
String
owner
!
String
greet(String
name)
{
"Hello
${name},
I
am
${owner}"
}
}
!
def
greeter
=
new
Greeter(owner:
"Guillaume")
!
println
greeter.greet("Marion")
!32
73. Native syntax constructs
//
closures
def
adder
=
{
a,
b
-‐>
a
+
b
}
!
//
lists
def
list
=
[1,
2,
3,
4,
5]
!
//
maps
def
map
=
[a:
1,
b:
2,
c:
3]
!
//
regular
expressions
def
regex
=
~/.*foo.*/
!
//
ranges
def
range
128..255
!33
74. Closures — the basics
def
adder
=
{
a,
b
-‐>
a
+
b
}
!
assert
adder(1,
2)
==
3
assert
adder('a',
'b')
==
'ab'
!34
75. Closures — the basics
Closure
parameters
def
adder
=
{
a,
b
-‐>
a
+
b
}
!
assert
adder(1,
2)
==
3
assert
adder('a',
'b')
==
'ab'
!34
76. Closures — the basics
Assign a function
into a variable
Closure
parameters
def
adder
=
{
a,
b
-‐>
a
+
b
}
!
assert
adder(1,
2)
==
3
assert
adder('a',
'b')
==
'ab'
!34
77. Closures — the basics
Assign a function
into a variable
Closure
parameters
def
adder
=
{
a,
b
-‐>
a
+
b
}
!
assert
adder(1,
2)
==
3
assert
adder('a',
'b')
==
'ab'
Short form of:
adder.call(‘a’, ‘b’)
!34
78. Closures — the basics
Assign a function
into a variable
Closure
parameters
def
adder
=
{
a,
b
-‐>
a
+
b
}
!
assert
adder(1,
2)
==
3
assert
adder('a',
'b')
==
'ab'
Short form of:
adder.call(‘a’, ‘b’)
!34
Genericity with
duck typing &
operator overloading
85. Closures — variable arguments
Variable number
of arguments
def
sum
=
{
...
elements
-‐>
elements.sum()
}
!
assert
sum(1,
2)
==
3
assert
sum('a',
'b',
'c')
==
'abc'
!37
86. Closures — variable arguments
You can specify
the type: int...
Variable number
of arguments
def
sum
=
{
...
elements
-‐>
elements.sum()
}
!
assert
sum(1,
2)
==
3
assert
sum('a',
'b',
'c')
==
'abc'
!37
87. Closures — default values
def
mult
=
{
int
a,
int
b
=
10
-‐>
a
*
b
}
!
assert
mult(2,
3)
==
6
assert
mult(5)
==
50
!38
88. Closures — default values
Default value
def
mult
=
{
int
a,
int
b
=
10
-‐>
a
*
b
}
!
assert
mult(2,
3)
==
6
assert
mult(5)
==
50
!38
89. Closures — default values
Default value
def
mult
=
{
int
a,
int
b
=
10
-‐>
a
*
b
}
!
assert
mult(2,
3)
==
6
assert
mult(5)
==
50
!38
Provided value
for b
90. Closures — default values
Default value
def
mult
=
{
int
a,
int
b
=
10
-‐>
a
*
b
}
!
assert
mult(2,
3)
==
6
assert
mult(5)
==
50
Default value
used for b
!38
Provided value
for b
94. Closures — map / filter / reduce
@groovy.transform.Immutable
class
Person
{
String
name
int
age
}
!40
95. Closures — map / filter / reduce
@groovy.transform.Immutable
class
Person
{
String
name
int
age
}
!40
96. Closures — map / filter / reduce
@groovy.transform.Immutable
class
Person
{
String
name
int
age
}
def
persons
=
[
new
Person('Guillaume',
36),
new
Person('Marion',
5),
new
Person('Erine',
1)
]
!40
97. Closures — map / filter / reduce
@groovy.transform.Immutable
class
Person
{
String
name
int
age
}
def
persons
=
[
new
Person('Guillaume',
36),
new
Person('Marion',
5),
new
Person('Erine',
1)
]
!40
98. Closures — map / filter / reduce
@groovy.transform.Immutable
class
Person
{
String
name
int
age
}
def
persons
=
[
new
Person('Guillaume',
36),
new
Person('Marion',
5),
new
Person('Erine',
1)
]
def
names
=
persons.findAll
{
it.age
<
18
}
.collect
{
it.name.toUpperCase()
}
.sort()
.join(',
')
!40
99. Closures — map / filter / reduce
@groovy.transform.Immutable
class
Person
{
String
name
int
age
}
def
persons
=
[
new
Person('Guillaume',
36),
new
Person('Marion',
5),
new
Person('Erine',
1)
]
def
names
=
persons.findAll
{
it.age
<
18
}
.collect
{
it.name.toUpperCase()
}
.sort()
.join(',
')
!40
100. Closures — map / filter / reduce
@groovy.transform.Immutable
class
Person
{
String
name
int
age
}
def
persons
=
[
new
Person('Guillaume',
36),
new
Person('Marion',
5),
new
Person('Erine',
1)
]
def
names
=
persons.findAll
{
it.age
<
18
}
.collect
{
it.name.toUpperCase()
}
.sort()
.join(',
')
assert
names
==
"ERINE,
MARION"
!40
101. Closures — map / filter / reduce
@groovy.transform.Immutable
class
Person
{
String
name
int
age
}
def
persons
=
[
new
Person('Guillaume',
36),
new
Person('Marion',
5),
new
Person('Erine',
1)
]
find/findAll, inject, collect,
flatten, min/max, unique,
reverse, collate, groupBy, any/
every, head/tail/last, count/
countBy, combinations/
permutations/subsequences/
transpose, withDefault/
withLazyDefault
def
names
=
persons.findAll
{
it.age
<
18
}
.collect
{
it.name.toUpperCase()
}
.sort()
.join(',
')
assert
names
==
"ERINE,
MARION"
!40
102. Closures — resource handling
new
File('bible.txt').withReader
{
r
-‐>
new
File('out.txt').withWriter
{
w
-‐>
r.eachLine
{
line
-‐>
if
(line.contains('Groovy'))
w
<<
line.toUpperCase()
}
}
}
!41
103. Closures — resource handling
Take care of properly
opening / closing resources
new
File('bible.txt').withReader
{
r
-‐>
new
File('out.txt').withWriter
{
w
-‐>
r.eachLine
{
line
-‐>
if
(line.contains('Groovy'))
w
<<
line.toUpperCase()
}
}
}
!41
104. Closures — custom control structures
void
unless(boolean
cond,
Closure
c)
{
if
(!cond)
c()
}
!
unless
(10
<
9)
{
println
"less"
}
!42
105. Closures — custom control structures
Closure as last argument
void
unless(boolean
cond,
Closure
c)
{
if
(!cond)
c()
}
!
unless
(10
<
9)
{
println
"less"
}
!42
106. Closures — custom control structures
Closure as last argument
void
unless(boolean
cond,
Closure
c)
{
if
(!cond)
c()
}
!
unless
(10
<
9)
{
println
"less"
}
!42
Equivalent to:
unless(10<9, {...})
107. Lists
def
list
=
['a',
'b',
'c']
!
list
<<
'd'
assert
list.contains('d')
!
assert
list.findAll
{
it.startsWith
'a'
}.size()
==
1
assert
list.collect
{
it.toUpperCase()
}
==
['A',
'B',
'C',
'D']
assert
list.inject('')
{
a,
b
-‐>
a
+
b
}
==
'abcd'
!43
108. Lists
List definition
def
list
=
['a',
'b',
'c']
!
list
<<
'd'
assert
list.contains('d')
!
assert
list.findAll
{
it.startsWith
'a'
}.size()
==
1
assert
list.collect
{
it.toUpperCase()
}
==
['A',
'B',
'C',
'D']
assert
list.inject('')
{
a,
b
-‐>
a
+
b
}
==
'abcd'
!43
109. Lists
List definition
def
list
=
['a',
'b',
'c']
!
Append an element
(operator overloading)
list
<<
'd'
assert
list.contains('d')
!
assert
list.findAll
{
it.startsWith
'a'
}.size()
==
1
assert
list.collect
{
it.toUpperCase()
}
==
['A',
'B',
'C',
'D']
assert
list.inject('')
{
a,
b
-‐>
a
+
b
}
==
'abcd'
!43
110. Lists
List definition
def
list
=
['a',
'b',
'c']
!
Append an element
(operator overloading)
list
<<
'd'
assert
list.contains('d')
!
assert
list.findAll
{
it.startsWith
'a'
}.size()
==
1
assert
list.collect
{
it.toUpperCase()
}
==
['A',
'B',
'C',
'D']
assert
list.inject('')
{
a,
b
-‐>
a
+
b
}
==
'abcd'
Functional-style
map / filter / reduce
with closures
!43
135. BigDecimal by default!
assert
2.0
-‐
1.1
==
0.9
assert
3
/
2
==
1.5
One of the reasons why microbenchmarks sometimes showed
Groovy to be slow...
!50
136. BigDecimal by default!
assert
2.0
-‐
1.1
==
0.9
assert
3
/
2
==
1.5
One of the reasons why microbenchmarks sometimes showed
Groovy to be slow...
!50
But you can use doubles & floats
for performance, with ‘d’ or ‘f ’
suffixes or with explicit type
139. Powerful
switch / case
on steroids
switch(obj)
{
case
123:
"number
123";
break
case
"abc":
"string
abc";
break
case
String:
"is
a
string";
break
case
[1,
2,
3]:
"in
list";
break
case
~/.*o+.*/:
"regex
match";
break
case
{
it
<
3
}:
"closure
criteria";
break
default:
"unknown"
}
144. Command chains
• Ability to chain method calls
without parentheses and dots
move
forward
at
3.km/h
!53
145. Command chains
• Ability to chain method calls
without parentheses and dots
move
forward
at
3.km/h
Actually equivalent to:
move(forward).at(3.getKm().div(h))
!53
147. Named arguments & command chains
check
that:
vodka
tastes
good
Will call:
check(that: vodka).tastes(good)
!54
148. Multiple assignment & destructuring
def
(a,
b)
=
['A',
'B']
!
(a,
b)
=
[b,
a]
!
def
(int
i,
int
j)
=
[1,
2]
!
def
geocode(String
place)
{
return
[45.4,
2.3]
}
!
def
(la,
lo)
=
geocode("Paris")
!
assert
la
==
45.4
&&
lo
==
2.3
!55
149. Multiple assignment & destructuring
def
(a,
b)
=
['A',
'B']
!
(a,
b)
=
[b,
a]
Classic « swap »
!
def
(int
i,
int
j)
=
[1,
2]
!
def
geocode(String
place)
{
return
[45.4,
2.3]
}
!
def
(la,
lo)
=
geocode("Paris")
!
assert
la
==
45.4
&&
lo
==
2.3
!55
150. Multiple assignment & destructuring
def
(a,
b)
=
['A',
'B']
!
With types
(a,
b)
=
[b,
a]
Classic « swap »
!
def
(int
i,
int
j)
=
[1,
2]
!
def
geocode(String
place)
{
return
[45.4,
2.3]
}
!
def
(la,
lo)
=
geocode("Paris")
!
assert
la
==
45.4
&&
lo
==
2.3
!55
151. Multiple assignment & destructuring
def
(a,
b)
=
['A',
'B']
!
With types
(a,
b)
=
[b,
a]
Classic « swap »
!
def
(int
i,
int
j)
=
[1,
2]
Method
returning a list
!
def
geocode(String
place)
{
return
[45.4,
2.3]
}
!
def
(la,
lo)
=
geocode("Paris")
!
assert
la
==
45.4
&&
lo
==
2.3
!55
152. Multiple assignment & destructuring
def
(a,
b)
=
['A',
'B']
!
With types
(a,
b)
=
[b,
a]
Classic « swap »
!
def
(int
i,
int
j)
=
[1,
2]
Method
returning a list
!
def
geocode(String
place)
{
return
[45.4,
2.3]
}
Destructuring
!
def
(la,
lo)
=
geocode("Paris")
!
assert
la
==
45.4
&&
lo
==
2.3
!55
153. Multiple assignment and destructuring
class
Point
{
double
x,
y
!
double
getAt(int
idx)
{
if
(idx
==
0)
x
else
if
(idx
==
1)
y
else
throw
new
Exception("Wrong
index")
}
}
!
def
(x,
y)
=
new
Point(x:
48.3,
y:
3.5)
!
assert
x
==
48.3
&&
y
==
3.5
!56
154. Multiple assignment and destructuring
class
Point
{
double
x,
y
Method signature
convention: getAt(int)
!
double
getAt(int
idx)
{
if
(idx
==
0)
x
else
if
(idx
==
1)
y
else
throw
new
Exception("Wrong
index")
}
}
!
def
(x,
y)
=
new
Point(x:
48.3,
y:
3.5)
!
assert
x
==
48.3
&&
y
==
3.5
!56
155. Multiple assignment and destructuring
class
Point
{
double
x,
y
Method signature
convention: getAt(int)
!
double
getAt(int
idx)
{
if
(idx
==
0)
x
else
if
(idx
==
1)
y
else
throw
new
Exception("Wrong
index")
}
}
Transparent destructuring
!
def
(x,
y)
=
new
Point(x:
48.3,
y:
3.5)
!
assert
x
==
48.3
&&
y
==
3.5
!56
156. Builders — JSON builder
import
groovy.json.*
!
def
json
=
new
JsonBuilder()
json.person
{
name
'Guillaume'
age
36
daughters
'Marion',
'Erine'
address
{
street
'1
Main
Street'
zip
75001
city
'Paris'
}
}
!57
157. Builders — JSON builder
import
groovy.json.*
Hierarchical data
def
json
=
new
JsonBuilder()
representation
!
json.person
{
name
'Guillaume'
age
36
daughters
'Marion',
'Erine'
address
{
street
'1
Main
Street'
zip
75001
city
'Paris'
}
}
!57
158. Builders — JSON builder
import
groovy.json.*
Hierarchical data
def
json
=
new
JsonBuilder()
representation
!
json.person
{
name
'Guillaume'
Closure blocks
age
36
delimiting
daughters
'Marion',
'Erine'
the
structure
address
{
street
'1
Main
Street'
zip
75001
city
'Paris'
}
}
!57
159. Builders — JSON builder
import
groovy.json.*
Hierarchical data
{
"person": {
def
json
=
new
JsonBuilder()
representation
!
"name": "Guillaume",
json.person
{
"age": 36,
name
'Guillaume'
[
"daughters":
Closure blocks
"Marion",
age
36
"Erine"
delimiting
daughters
'Marion',
'Erine'
the
],
structure
"address": {
address
{
"street": "1 Main Street",
street
'1
Main
Street'
"zip": 75001,
"city":
zip
75001
"Paris"
}
city
'Paris'
}
}
}
}
!57
160. GPath expressions
• GPath expressions are like XPath
but for an object graph
import
groovy.json.*
!
def
url
=
"https://api.github.com/repos/groovy/groovy-‐core/commits"
!
def
commits
=
new
JsonSlurper().parseText(url.toURL().text)
!
assert
commits[0].commit.author.name
==
'Cedric
Champeau'
!58
161. GPath expressions
• GPath expressions are like XPath
but for an object graph
import
groovy.json.*
!
def
url
=
"https://api.github.com/repos/groovy/groovy-‐core/commits"
!
def
commits
=
new
JsonSlurper().parseText(url.toURL().text)
!
assert
commits[0].commit.author.name
==
'Cedric
Champeau'
GPath expression
!58
162. GPath expressions
• GPath expressions are like XPath
but for an object graph
import
groovy.json.*
!
def
url
=
"https://api.github.com/repos/groovy/groovy-‐core/commits"
!
def
commits
=
new
JsonSlurper().parseText(url.toURL().text)
!
assert
commits[0].commit.author.name
==
'Cedric
Champeau'
GPath expression
!58
Add find / findAll
into the mix
163. GPath expressions
• GPath expressions are like XPath
No (un)marshalling!
but for an object graph
import
groovy.json.*
!
def
url
=
"https://api.github.com/repos/groovy/groovy-‐core/commits"
!
def
commits
=
new
JsonSlurper().parseText(url.toURL().text)
!
assert
commits[0].commit.author.name
==
'Cedric
Champeau'
GPath expression
!58
Add find / findAll
into the mix
164. Power asserts
def
(a,
b,
c)
=
[20,
30,
40]
!
assert
a
*
(b
-‐
1)
/
10
==
3
*
c
/
2
+
1
!59
166. Power asserts
def
(a,
b,
c)
=
[20,
30,
40]
!
assert
a
*
(b
-‐
1)
/
10
==
3
*
c
/
2
+
1
Assertion
failed:
!
assert
a
*
(b
-‐
1)
/
10
==
3
*
c
/
2
+
1
|
|
|
|
|
|
|
|
|
|
|
580|
29
58
false|
|
60
61
20
30
|
40
120
!
Invented by the Spock testing framework
at
script1.run(script1.groovy:3)
!59
167. Null handling
class
Order
{
LineItem
line
}
class
LineItem
{
int
quantity
Item
item
}
class
Item
{
String
name
}
!
def
o
=
new
Order(
line:
new
LineItem(
quantity:
2,
item:
null))
!
println
o.line.item.name
!60
168. Null handling
class
Order
{
LineItem
line
}
class
LineItem
{
int
quantity
Item
item
}
class
Item
{
String
name
}
!
With Java, you only
get an NPE. No idea
where it came from!
def
o
=
new
Order(
line:
new
LineItem(
quantity:
2,
item:
null))
!
println
o.line.item.name
!60
169. Null handling
class
Order
{
LineItem
line
}
class
LineItem
{
int
quantity
Item
item
}
class
Item
{
String
name
}
!
With Java, you only
get an NPE. No idea
where it came from!
def
o
=
new
Order(
line:
new
LineItem(
quantity:
2,
item:
null))
!
println
o.line.item.name
!60
Groovy will say:
Cannot get property
‘name’ on null object
170. Null handling
class
Order
{
LineItem
line
}
class
LineItem
{
int
quantity
Item
item
}
class
Item
{
String
name
}
!
def
o
=
new
Order(
line:
new
LineItem(
quantity:
2,
item:
null))
!
println
o.line.item.name
!60
171. Null handling
class
Order
{
LineItem
line
}
class
LineItem
{
int
quantity
Item
item
}
class
Item
{
String
name
}
o?.line?.item?.name
!
def
o
=
new
Order(
line:
new
LineItem(
quantity:
2,
item:
null))
!
println
o.line.item.name
!60
172. Null handling
class
Order
{
LineItem
line
}
class
LineItem
{
int
quantity
Item
item
}
class
Item
{
String
name
}
o?.line?.item?.name
!
def
o
=
new
Order(
line:
new
LineItem(
quantity:
2,
item:
null))
!
println
o.line.item.name
!60
Safe navigation:
will just return
null; No NPE
186. Towards Elvis...
def
(x,
y)
=
['MacBook
Pro',
'unknown']
if
(x
!=
null
&&
x.size()
>
0)
x
else
y
!65
187. Towards Elvis...
def
(x,
y)
=
['MacBook
Pro',
'unknown']
if
(x
!=
null
&&
x.size()
>
0)
x
else
y
if
(x
&&
x.size())
x
else
y
!65
188. Towards Elvis...
def
(x,
y)
=
['MacBook
Pro',
'unknown']
if
(x
!=
null
&&
x.size()
>
0)
x
else
y
if
(x
&&
x.size())
x
else
y
if
(x)
x
else
y
!65
189. Towards Elvis...
def
(x,
y)
=
['MacBook
Pro',
'unknown']
if
(x
!=
null
&&
x.size()
>
0)
x
else
y
if
(x
&&
x.size())
x
else
y
if
(x)
x
else
y
x
?
x
:
y
!65
190. Towards Elvis...
def
(x,
y)
=
['MacBook
Pro',
'unknown']
if
(x
!=
null
&&
x.size()
>
0)
x
else
y
if
(x
&&
x.size())
x
else
y
if
(x)
x
else
y
x
?
x
:
y
x
?:
y
!65
191. Towards Elvis...
def
(x,
y)
=
['MacBook
Pro',
'unknown']
if
(x
!=
null
&&
x.size()
>
0)
x
else
y
if
(x
&&
x.size())
x
else
y
if
(x)
x
else
y
x
?
x
:
y
x
?:
y
Null, empty, zerosized... false,
otherwise true!
!65
192. Towards Elvis...
def
(x,
y)
=
['MacBook
Pro',
'unknown']
if
(x
!=
null
&&
x.size()
>
0)
x
else
y
if
(x
&&
x.size())
x
else
y
if
(x)
x
else
y
x
?
x
:
y
x
?:
y
Good old ternary
operator
!65
Null, empty, zerosized... false,
otherwise true!
193. Towards Elvis...
def
(x,
y)
=
['MacBook
Pro',
'unknown']
if
(x
!=
null
&&
x.size()
>
0)
x
else
y
if
(x
&&
x.size())
x
else
y
if
(x)
x
else
y
x
?
x
:
y
x
?:
y
Elvis!
!65
Good old ternary
operator
Null, empty, zerosized... false,
otherwise true!
194. AST transformations
• Abstract Syntax Tree
– in memory representation of your program
before being compiled into bytecode
!
• AST transformation == process of transforming
the AST of a program before it’s compiled
!
• Macro-like compiler hook!
!66
197. Lots of AST transformations...
• Dependencies handling
– @Grab, @GrabConfig, @GrabExclude,
@GrabResolver
• Test assistance
– @NotYetImplemented, @ASTTest
!69
198. Immutability
• Implement immutability
by the book
!
– final class
– tuple-style constructor
– private final backing fields
– defensive copying of collections
– equals() and hashCode() methods
– toString() method
– ...
!70
199. Immutability
• Implement immutability
by the book
!
– final class
Can be error-prone to
– tuple-style constructor
write immutable
– private final backing fields
classes oneself!
– defensive copying of collections
– equals() and hashCode() methods
– toString() method
– ...
!70
200. Immutability
• A Person class with
– a String name
– an int age
public final class Person {!
private final String name;!
private final int age;!
!
!
!
!
!
!
}!
!71
public Person(String name, int age) {!
this.name = name;!
this.age = age;!
}!
public String getName() {!
return name;!
}!
public int getAge() {!
return age;!
}!
public int hashCode() {!
return age + 31 * name.hashCode();!
}!
public boolean equals(Object other) {!
if (other == null) {!
return false;!
}!
if (this == other) {!
return true;!
}!
if (Person.class != other.getClass()) {!
return false;!
}!
Person otherPerson = (Person)other;!
if (!name.equals(otherPerson.getName()) {!
return false;!
}!
if (age != otherPerson.getAge()) {!
return false;!
}!
return true;!
}!
public String toString() {!
return "Person(" + name + ", " + age + ")";!
}!
201. Immutability
• A Person class with
– a String name
– an int age
public final class Person {!
private final String name;!
private final int age;!
!
!
!
!
!
!
}!
!71
Damn
verbose
Java!
public Person(String name, int age) {!
this.name = name;!
this.age = age;!
}!
public String getName() {!
return name;!
}!
public int getAge() {!
return age;!
}!
public int hashCode() {!
return age + 31 * name.hashCode();!
}!
public boolean equals(Object other) {!
if (other == null) {!
return false;!
}!
if (this == other) {!
return true;!
}!
if (Person.class != other.getClass()) {!
return false;!
}!
Person otherPerson = (Person)other;!
if (!name.equals(otherPerson.getName()) {!
return false;!
}!
if (age != otherPerson.getAge()) {!
return false;!
}!
return true;!
}!
public String toString() {!
return "Person(" + name + ", " + age + ")";!
}!
202. Immutability
• A Person class with
– a String name
– an int age
Damn
verbose
Java!
public final class Person {!
private final String name;!
private final int age;!
!
!
!
!
!
public Person(String name, int age) {!
this.name = name;!
this.age = age;!
}!
public String getName() {!
return name;!
}!
public int getAge() {!
return age;!
}!
public int hashCode() {!
return age + 31 * name.hashCode();!
}!
public boolean equals(Object other) {!
if (other == null) {!
return false;!
}!
if (this == other) {!
return true;!
}!
if (Person.class != other.getClass()) {!
return false;!
}!
Person otherPerson = (Person)other;!
if (!name.equals(otherPerson.getName()) {!
return false;!
}!
if (age != otherPerson.getAge()) {!
return false;!
}!
return true;!
}!
Although it’s also a valid
Groovy program!
!
}!
!71
public String toString() {!
return "Person(" + name + ", " + age + ")";!
}!
204. Memoization
• Cache the result of previous invocations of
closures or methods with the same set
of argument values
import
groovy.transform.*
!
@Memoized
long
fib(long
n)
{
if
(n
==
0)
0
else
if
(n
==
1)
1
else
fib(n
-‐
1)
+
fib(n
-‐
2)
}
!
println
fib(40)
!73
205. Memoization
• Cache the result of previous invocations of
closures or methods with the same set
of argument values
import
groovy.transform.*
Best applied to
side-effect free
functions
!
@Memoized
long
fib(long
n)
{
if
(n
==
0)
0
else
if
(n
==
1)
1
else
fib(n
-‐
1)
+
fib(n
-‐
2)
}
!
println
fib(40)
!73
209. Groovy allows you
to be lazy
More concise, more
readable code
The compiler will do
the job for you
210. Groovy allows you
to be lazy
More concise, more
readable code
The compiler will do
the job for you
Less stuff to maintain
and worry about
211. @TypeChecked & @CompileStatic
• Static type checking with @TypeChecked,
throws compilation errors on...
– typos in method and variable names
– incompatible return types
– wrong type assignments
!
• Supports fine-grained type inference
– « Least Upper Bound »
– « Flow typing »
!75
212. @TypeChecked & @CompileStatic
• Static type checking with @TypeChecked,
throws compilation errors on...
– typos in method and variable names
– incompatible return types
You can even extend the
– wrong type assignments
static type checker!
!
• Supports fine-grained type inference
– « Least Upper Bound »
– « Flow typing »
!75
213. @TypeChecked & @CompileStatic
• Static type checking with @TypeChecked,
throws compilation errors on...
– typos in method and variable names
– incompatible return types
You can even extend the
– wrong type assignments
!
static type checker!
• Supports fine-grained type inference
– « Least Upper Bound »
– « Flow typing »
Type check DSLs or
dynamic features!
!75
214. @TypeChecked & @CompileStatic
!
• What is type checked can also be compiled
statically with @CompileStatic
!
– generate the same bytecode as javac
!
– same performance as Java
!76
215. Pi (π)
Fibonacci
quadrature
Java
191 ms
97 ms
3.6 s
2.x
Static
compilation
197 ms
101 ms
4.3 s
1.8
!77
Binary
trees
Primitive
optimizations
360 ms
111 ms
23.7 s
1.7
Static compilation performance
No prim.
optimizations
2590 ms 3220 ms
50.0 s
226. GVM: Groovy enVironment Manager
• The new kid on the block
– http://gvmtool.net/ — @gvmtool
!
• Manage parallel versions
of the various ecosystem projects
!
• Supports...
– Groovy, Grails, Griffon, Gradle,Vert.x, Spring Boot
!
• On Linux, MacOS, Cygwin, Solaris, FreeBSD
!86
232. Spock example
@Grab a dependency
@Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0')
import
spock.lang.*
!
class
MathSpec
extends
Specification
{
def
"maximum
of
two
numbers"()
{
expect:
Math.max(a,
b)
==
c
!
where:
a
|
b
||
c
1
|
3
||
3
7
|
4
||
4
0
|
0
||
0
}
}
!88
233. Spock example
@Grab a dependency
@Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0')
import
spock.lang.*
!
class
MathSpec
extends
Specification
{
def
"maximum
of
two
numbers"()
{
expect:
Math.max(a,
b)
==
c
!
where:
a
|
b
||
c
1
|
3
||
3
7
|
4
||
4
0
|
0
||
0
}
}
!88
Meaningful test
method names
234. Spock example
@Grab a dependency
@Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0')
import
spock.lang.*
!
Meaningful test
method names
class
MathSpec
extends
Specification
{
def
"maximum
of
two
numbers"()
{
expect:
Math.max(a,
b)
==
c
Clever use of labels
!
where:
a
|
b
||
c
1
|
3
||
3
7
|
4
||
4
0
|
0
||
0
}
}
!88
for BDD style
235. Spock example
@Grab a dependency
@Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0')
import
spock.lang.*
!
Meaningful test
method names
class
MathSpec
extends
Specification
{
def
"maximum
of
two
numbers"()
{
expect:
Math.max(a,
b)
==
c
Clever use of labels
!
where:
a
|
b
||
c
1
|
3
||
3
7
|
4
||
4
0
|
0
||
0
}
}
!88
for BDD style
Expression to
be asserted
236. Spock example
@Grab a dependency
@Grab('org.spockframework:spock-‐core:0.7-‐groovy-‐2.0')
import
spock.lang.*
Meaningful test
method names
!
class
MathSpec
extends
Specification
{
def
"maximum
of
two
numbers"()
{
expect:
Math.max(a,
b)
==
c
Clever use of labels
for BDD style
!
where:
a
|
b
||
c
1
|
3
||
3
7
|
4
||
4
0
|
0
||
0
}
}
!88
Cute datadriven tests!
Expression to
be asserted
242. Geb — Example
Drive the browser
to this site
import
geb.Browser
!
Browser.drive
{
go
"http://myapp.com/login"
!
assert
$("h1").text()
==
"Please
Login"
!
$("form.login").with
{
username
=
"admin"
password
=
"password"
login().click()
}
!
assert
$("h1").text()
==
"Admin
Section"
}
!91
243. Geb — Example
Drive the browser
to this site
import
geb.Browser
!
Browser.drive
{
go
"http://myapp.com/login"
Check the content
of the title
!
assert
$("h1").text()
==
"Please
Login"
!
$("form.login").with
{
username
=
"admin"
password
=
"password"
login().click()
}
!
assert
$("h1").text()
==
"Admin
Section"
}
!91
244. Geb — Example
Drive the browser
to this site
import
geb.Browser
!
Browser.drive
{
go
"http://myapp.com/login"
Check the content
of the title
!
assert
$("h1").text()
==
"Please
Login"
!
$("form.login").with
{
username
=
"admin"
password
=
"password"
login().click()
}
!
assert
$("h1").text()
==
"Admin
Section"
}
!91
Find & fill in
the form
245. Geb — Example
Drive the browser
to this site
import
geb.Browser
!
Browser.drive
{
go
"http://myapp.com/login"
Check the content
of the title
!
assert
$("h1").text()
==
"Please
Login"
!
$("form.login").with
{
username
=
"admin"
password
=
"password"
login().click()
}
!
assert
$("h1").text()
==
"Admin
Section"
}
!91
Find & fill in
the form
Submit the
form
246. Geb — Example
Drive the browser
to this site
import
geb.Browser
!
Browser.drive
{
go
"http://myapp.com/login"
Check the content
of the title
!
assert
$("h1").text()
==
"Please
Login"
!
$("form.login").with
{
username
=
"admin"
password
=
"password"
login().click()
}
!
assert
$("h1").text()
==
"Admin
Section"
}
Find & fill in
the form
Submit the
form
In the admin section, yeah!
!91
247. Geb — With page objects and Spock
import
geb.spock.GebSpec
!
class
GoogleWikipediaSpec
extends
GebSpec
{
!
def
"first
result
for
wikipedia
search
should
be
wikipedia"()
{
given:
to
GoogleHomePage
!
expect:
at
GoogleHomePage
!
when:
search.field.value("wikipedia")
!
then:
waitFor
{
at
GoogleResultsPage
}
!
and:
firstResultLink.text()
==
"Wikipedia"
!
when:
firstResultLink.click()
!
then:
waitFor
{
at
WikipediaPage
}
}
}
!92
248. Geb — With page objects and Spock
import
geb.spock.GebSpec
!
With page objects
def
"first
result
for
wikipedia
search
should
be
wikipedia"()
{
class
GoogleWikipediaSpec
extends
GebSpec
{
!
given:
to
GoogleHomePage
!
expect:
at
GoogleHomePage
!
when:
search.field.value("wikipedia")
!
then:
waitFor
{
at
GoogleResultsPage
}
!
and:
firstResultLink.text()
==
"Wikipedia"
!
when:
firstResultLink.click()
!
then:
waitFor
{
at
WikipediaPage
}
}
}
!92
249. Geb — With page objects and Spock
import
geb.spock.GebSpec
!
With page objects
def
"first
result
for
wikipedia
search
should
be
wikipedia"()
{
class
GoogleWikipediaSpec
extends
GebSpec
{
!
given:
to
GoogleHomePage
!
expect:
at
GoogleHomePage
BDD style: given/when/then
!
when:
search.field.value("wikipedia")
!
then:
waitFor
{
at
GoogleResultsPage
}
!
and:
firstResultLink.text()
==
"Wikipedia"
!
when:
firstResultLink.click()
!
then:
waitFor
{
at
WikipediaPage
}
}
}
!92
250. Geb — With page objects and Spock
import
geb.spock.GebSpec
!
With page objects
def
"first
result
for
wikipedia
search
should
be
wikipedia"()
{
class
GoogleWikipediaSpec
extends
GebSpec
{
!
given:
to
GoogleHomePage
!
expect:
at
GoogleHomePage
BDD style: given/when/then
!
when:
search.field.value("wikipedia")
!
Wait for slow loading pages
then:
waitFor
{
at
GoogleResultsPage
}
!
and:
firstResultLink.text()
==
"Wikipedia"
!
when:
firstResultLink.click()
!
then:
waitFor
{
at
WikipediaPage
}
}
}
!92
252. Java’s best friend
• Java derived syntax
–Flat learning curve
–Easy to learn
• But goes beyond Java
–Concise, expressive, readable
–Fit for Domain-Specific Languages
• Seamless & transparent Java integration
–Mix & match Groovy & Java classes (joint compil.)
–No language barrier to cross
!94
253. Groovy’s nature
!
• Object oriented dynamic language...
!
• But...
– as type safe as you want it — static type checking
– as fast as you need it — static compilation
– as functional as you make it — closures...
!95
254. Groovy use cases
• Scripting tasks, build automation
• Extension points for customizing/configuring apps
• Business languages & Domain-Specific Languages
• Full blown apps
– for desktop with Griffon
– for the web with Grails, Ratpack, Gaelyk
– for web reactive programming with Reactor
!96