$ curl -s get.gvmtool.net | bash
Dan Hyun
Dan Hyun
Software Engineer Mindspark
Introduction to Groovy
Tools and tips for working with Groovy
Amazing general purpose programming language
Productivity
Readability
Happiness
Know Java? You already know Groovy
Compiles to JVM bytecode
Fully interoperable with Java
Amazing OSS Projects
Active twitter participation
Great tools and libraries
Download binaries
Or use GVM (Recommended )
$ curl -s get.gvmtool.net | bash
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
public class HelloWorld {
public static void main(String[] args) {
System.out.println("Hello, World!") (1)
}
}
1 | Drop the semicolon! |
class HelloWorld { (1)
static void main(String[] args) { (1)
System.out.println("Hello, World!")
}
}
1 | Drop public, everything is public in Groovy |
class HelloWorld {
static void main(String[] args) {
println("Hello, World!") (1)
}
}
1 | Drop System.out |
class HelloWorld {
static void main(String[] args) {
println "Hello, World!" (1)
}
}
1 | Drop parentheses |
println "Hello, World!" (1)
1 | Don’t need a main class wrapper to run code |
$ groovy HelloWorld.groovy
Hello, World!
$ groovy -e ' println "Hello, World!" ' #1
Hello, World!
1 | Skip the file altogether |
$ groovyConsole
Invokes
Groovy is always compiled before execution
$ groovyc HelloWorld.groovy && ls -l HelloWorld*
-rw-r--r-- 1 danny Administ 5152 May 27 22:53 HelloWorld.class
-rw-r--r-- 1 danny Administ 87 May 27 22:46 HelloWorld.groovy
$ java -cp ".:$GROOVY_HOME/embeddable/groovy-all-2.4.3.jar" HelloWorld
Hello, World!
Express yourself
println 'I\'m a string'
println "Me too"
println(/So am I!/)
I'm a string
Me too
So am I!
println "GStrings are created with a pair of \""
String value = "interpolation"
println "They allow for easy $value"
GStrings are created with a pair of "
They allow for easy interpolation
println 'You ' + "can " + /concatenate / + 'Strings'
println ("You can't remove portions of strings" - "'t")
You can concatenate Strings
You can remove portions of strings
Everything is a method call
Operators invoke method calls
assert 1 + 1 == 2
assert 1.plus(1) == 2
assert 'a' + 'b' == "ab" (1)
assert ('a'.plus('b')).equals("ab")
1 | == invokes .equals() |
class GroovyBeer {
String brewer
String name
GroovyBeer plus(GroovyBeer gb) {
[brewer: brewer + gb.brewer, name: name + gb.name] (1)
}
}
1 | Implicit return |
GroovyBeer beer1 = [brewer: 'GR8', name: 'Groovy ']
GroovyBeer beer2 = [brewer: 'Conf', name: 'IPA']
GroovyBeer groovyiestBeer = beer1 + beer2
assert groovyiestBeer.brewer == 'GR8Conf'
assert groovyiestBeer.name == 'Groovy IPA'
public class Example {
public static void main(String[] args) {
List<GroovyBeer> beers = new ArrayList<>();
GroovyBeer b1 = new GroovyBeer();
b1.setBrewer("GR8Conf");
b1.setName("Groovy Porter");
beers.add(b1);
GroovyBeer b2 = new GroovyBeer();
b2.setBrewer("GR8Conf");
b2.setName("Groovy Gose");
beers.add(b2);
for(GroovyBeer b: beers) {
System.out.println("I am enjoying a " + b.getName() + " by " + b.getBrewer());
}
// Java 8 Flavor
// beers.stream()
// .forEach(b ->
// System.out.println("I am enjoying a " + b.getName() + " by " + b.getBrewer()));
}
}
List<GroovyBeer> beers = [] (1)
beers << new GroovyBeer(brewer: 'GR8Conf', name: 'Groovy Porter') (2)
beers << new GroovyBeer(brewer: 'GR8Conf', name: 'Groovy Gose') (2)
beers.each { b -> println "I am enjoying a $b.name by $b.brewer" } (3)
1 | List literal syntax! ArrayList by default |
2 | Invoke .leftShift() to add to beer list |
3 | Apply a closure to each beer in beer list |
List<String> l1 = ['a', 'b'] (1)
List<String> l2 = ['c', 'd'] (1)
List l3 = l1 + l2 (2)
assert l3 == ['a', 'b', 'c', 'd'] (3)
1 | List creation |
2 | List Concatenation |
3 | List equality based on elements and order of elements |
public class Example {
public static void main(String[] args) {
Map<String, GroovyBeer> beerByType = new HashMap<>();
GroovyBeer pils = new GroovyBeer();
pils.setName("Groovy Pils");
beerByType.put("light", pils);
GroovyBeer sour = new GroovyBeer();
sour.setName("Groovy Flemish Ale");
beerByType.put("sour", sour);
beerByType.entrySet()
.stream().forEach(e ->
System.out.println("Enjoy " + e.getValue().getName() + ", a " + e.getKey() + " beer."));
}
}
Map<String, GroovyBeer> beerByType = [:] (1)
beerByType.pils = new GroovyBeer(name: 'Groovy Pils') (2)
beerByType.sour = new GroovyBeer(name: 'Groovy Flemish Ale') (2)
beerByType.each { e -> println "Enjoy $e.value.name, a $e.key beer"} (3)
1 | Map literal syntax, LinkedHashMap by default |
2 | Populate map as if assigning properties |
3 | Apply closure to entry set |
Equality
assert [a: 'foo', b: 'bar'] == [b: 'bar', a: 'foo'] (1)
1 | Equality is based on entries, order doesn’t matter |
public class Beer {
private String brewer;
private String name;
public String getBrewer() { return brewer; }
public void setBrewer(String brewer) { this.brewer = brewer; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
Beer beer = new Beer()
beer.setBrewer("GR8Conf")
beer.setName("Groovy Stout")
assert beer.getBrewer() == "GR8Conf"
assert beer.getName() == "Groovy Stout"
Beer beer = new Beer()
beer.brewer = "GR8Conf" (1)
beer.name = "Groovy Stout" (1)
assert beer.brewer == "GR8Conf" (2)
assert beer.name == "Groovy Stout" (2)
1 | Set as if property |
2 | Get as if property |
Beer beer = new Beer(brewer: "GR8Conf", name: "Groovy Stout") (1)
assert beer.brewer == "GR8Conf"
assert beer.name == "Groovy Stout"
1 | Pass map literal to constructor |
Beer beer = [brewer: "GR8Conf", name: "Groovy Stout"] (1)
assert beer.brewer == "GR8Conf"
assert beer.name == "Groovy Stout"
1 | Use a map as a constructor |
Drop public access modifier and semicolons
class GroovyBeer {
private String brewer
private String name
String getBrewer() { return brewer }
void setBrewer(String brewer) { this.brewer = brewer }
String getName() { return name }
void setName(String name) { this.name = name }
}
Properties are automatically private
Setters/getters are generated
class GroovyBeer {
String brewer
String name
}
public class Example {
public static void main(String[] args) {
GroovyBeer groovyBeer = new GroovyBeer();
groovyBeer.setBrewer("GR8Conf");
groovyBeer.setName("Groovy Ale");
assert groovyBeer.getBrewer().equals("GR8Conf");
assert groovyBeer.getName().equals("Groovy Ale");
}
}
null
String getSomeDeeplyNestedItem(def someItem) {
someItem?.foo?.bar?.doesThisExist?.whoCares?.areWeSafe?.desiredItem (1)
}
assert null == getSomeDeeplyNestedItem(null)
assert null == getSomeDeeplyNestedItem([someItem: null])
assert 'yay!' == getSomeDeeplyNestedItem(
[foo:
[bar:
[doesThisExist:
[whoCares:
[areWeSafe:
[desiredItem: 'yay!']]]]]])
null
String getValueOrDefault(String s) {
s ?: 'Sorry' (1)
}
assert 'Sorry' == getValueOrDefault(null)
assert 'Sorry' == getValueOrDefault('')
assert ' ' == getValueOrDefault(' ')
assert 'Hurray' == getValueOrDefault('Hurray')
1 | Elvis (else if) operator, if s is Groovy False return right hand operand |
Object that captures initialized state and succinctly defines behavior
String value = 'world!'
Closure c = { "$it, $value" } (1)
assert 'Hello, world!' == c('Hello') (2)
assert 'Hello, world!' == c.call('Hello') (3)
1 | Closure literal syntax, it is implicit argument |
2 | Invoke closure directory |
3 | Invoke closure via .call() |
Closure adder = { a, b -> a + b }
assert 3 == adder(1, 2)
Closure plusOne = adder.curry(1)
assert 3 == plusOne(2)
Groovy enables functional programming
def list = [1,2,3,4,5]
assert 15 == list.sum() (1)
assert [1,2,3] == list.findAll { it < 4 } (2)
assert [0,1,2,3,4] == list.collect { it - 1 } (3)
assert '1, 2, 3, 4, 5' == list.join(', ') (4)
assert 120 == list.inject(1) { sum, i -> sum * i } (5)
1 | Sum all elements, accepts closure as variant to return derived value for summation |
2 | Inclusive filter for all elements that satisfy predicate |
3 | Akin to .map() |
4 | Concatenates to String |
5 | Reduce elements to multiplicative product |
$ git clone git@github.com:nadavc/groovykoans.git
$ cd groovykoans
$ ./gradlew tasks
... lots of output ...
Other tasks
-----------
removeSolutions - Removes the solutions from all Koans, so you can start fresh!
wrapper
Rules
-----
... more output ...
Pattern: koan<Number>: Runs a single Koan
$ ./gradlew removeSolutions
Removed 73 solutions from Koans. Good luck!
$ ./gradlew clean check -i
Groovy Koans 0.5:
The truth is out there. Anybody got the URL?
--------------------------------------------
... lots of output ...
52 tests completed, 50 failed, 2 skipped
FAILURE: Build failed with an exception.
Set up for use with IDE
$ ./gradlew idea
$ ./gradlew eclipse
Use dynamic Gradle task
./gradlew tasks
outputRules
-----
Pattern: koan<Number>: Runs a single Koan
$ ./gradlew koan01
Groovy Koans 0.5:
Latest survey shows that 3 out of 4 people make up 75% of the world's population.
---------------------------------------------------------------------------------
Running exercises in test03_MapsInGroovy()..................FAILURE
Running exercises in test05_ElvisAndSafeNavigation()........FAILURE
Running exercises in test02_GStrings()......................FAILURE
Running exercises in test04_Lists().........................FAILURE
Running exercises in test01_AssertionsAndSomeSyntax().......SUCCESS
5 tests completed, 4 failed
public class Example {
public static void main(String[] args) throws IOException {
List<String> buildFile = Files
.lines(Paths.get("build.gradle"))
.map(s -> "GR8Conf " + s)
.collect(Collectors.toList()); (1)
File tmp = File.createTempFile("blah", ".tmp");
tmp.deleteOnExit();
Files.write(tmp.toPath(), buildFile); (2)
assert buildFile.size() == Files.readAllLines(tmp.toPath()).size();
assert Files.lines(tmp.toPath())
.allMatch(s -> s.startsWith("GR8Conf ")); (3)
}
}
1 | Prepend `GR8Conf ` to all lines in file |
2 | Write new lines to a temp File |
3 | All lines written are prefixed with `GR8Conf ` |
def newLines = new File('D:/projects/gr8conf-2015-groovy-safari/build.gradle')
.readLines()
.collect { "GR8Conf $it" } (1)
def tmpFile = File.createTempFile('blah', '.tmp')
tmpFile.deleteOnExit()
tmpFile.withPrintWriter { w ->
newLines.each { l -> w.println l } (2)
}
assert newLines.size() == tmpFile.readLines().size()
assert tmpFile.eachLine { it.startsWith 'GR8Conf ' } (3)
1 | Prepend `GR8Conf ` to all lines in file |
2 | Write new lines to a temp File |
3 | All lines written are prefixed with `GR8Conf ` |
String gr8ConfHTML = 'http://gr8conf.eu'.toURL().text
assert gr8ConfHTML.length()
assert gr8ConfHTML
.startsWith('<!doctype html> <html class="no-js" ng-app="gr8conf2015">')
import groovy.json.JsonSlurper
def response = 'https://api.github.com/orgs/groovy/repos'.toURL().text
def repos = new JsonSlurper().parseText response (1)
assert repos.size()
assert repos.name (2)
.sort {a, b -> a.toLowerCase() <=> b.toLowerCase() } ==
['artwork', 'gmaven', 'GMavenPlus',
'groovy-android-gradle-plugin', 'groovy-core', 'groovy-eclipse',
'groovy-website', 'groovy-windows-installer']
1 | Parse response text as JSON |
2 | Use dot notation to navigate object graph |
import groovy.sql.Sql
Sql sql = Sql.newInstance(
url: 'jdbc:h2:mem:db',
driver: 'org.h2.Driver',
user: 'sa', password: '')
sql.execute('create table beer (name char(255))')
def beers = ['Lager', 'Pale Ale', 'Saison', 'Black IPA']
beers.each { b ->
sql.execute('INSERT INTO `beer` (name) VALUES (?)', b) (1)
}
def rows = sql.rows('select * from beer') (2)
assert rows.size() == beers.size()
assert rows.name == beers (3)
1 | Insert each beer name into the db |
2 | Simple select, returns List<GroovyRowResult> |
3 | Use simple dot notation to access name key in each result |