groovy, in the light of java 8
DESCRIPTION
With Java 8 out the door, Java developers can, at last, benefit from the long-awaited lambdas, to taste the newly-found functional flavor of the language. Streams are there to work more easily and efficiently with heaps of data. Those things are not new to developers acquainted with Groovy. But what is left to Groovy to make it attractive beyond all the aspects Java has caught up with Groovy?TRANSCRIPT
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Groovy, in the light of Java 8Guillaume Laforge — Groovy project lead / Pivotal
@glaforge
Stay up-to-date
Groovy Weekly Newsletter (Every Tuesday) http://beta.groovy-lang.org/groovy-weekly.html
2
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Goal of this talk
A recurring question…
Do we still need Groovy now that we
have Java 8?
6
To those who said no…
7
But more precisely…
• Will Java lambdas replace Groovy closures? • What are the differences between them? !
• Will Groovy support all the new Java 8 language constructs? • lambdas, default methods, method references… !
• How Groovy developers can benefit from Java 8? !
• What does Groovy offer beyond Java 8?
8
What about redundancy?
!
!
!
!
!
• Closures • Traits • Truth & null handling • Functional with collections • Method closures
!
!
!
!
!
!
!
• Lambdas • Default methods • Optional • Stream API • Method references
9
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Agenda
What we’re going to talk about
• What’s new in Java 8 • new syntax constructs • new APIs !
• Similar concepts in Groovy • and how they compare or complement !
• What Groovy offers in addition • beyond Java, Groovy adds its own twist!
11
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
What’s new in Java 8?
This is not a Java 8 crash courses with all the details :-)
This is not a Java 8 crash courses with all the details :-)
We want to understand the implications with regards to Groovy
What’s new in Java 8?
!
• New syntax • Streams • Profiles • Security enhancements • JavaFX • Tools • i18n • Date / time API • Nashorn / JavaScript
!
!
!
• Pack200 • IO / NIO improvements • New utility classes • JDBC • Networking • Concurrency • JAXP • Hotspot • Java Mission Control
14
What’s new in Java 8?
!
• New syntax • Streams • Profiles • Security enhancements • JavaFX • Tools • i18n • Date / time API • Nashorn / JavaScript
!
!
!
• Pack200 • IO / NIO improvements • New utility classes • JDBC • Networking • Concurrency • JAXP • Hotspot • Java Mission Control
14
Great concise resource with the whole list:http://bit.ly/new-in-java-8
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Support the new Java 8
language features
New Java 8 syntax
New Java 8 syntax constructs
• Lambda expressions • Method references • Static & default methods in interfaces • Repeating annotations • Annotations on types • Improved type inference • Method parameter reflection
16
New Java 8 syntax constructs
• Lambda expressions • Method references • Static & default methods in interfaces • Repeating annotations • Annotations on types • Improved type inference • Method parameter reflection
17
Lambda expressions
19
double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max();
Lambda expressions
20
double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max();
Lambda expressions
20
double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max();
Coercion into a Predicate « functional interface »
Lambda expressions
20
double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max();
There’s no function type!
Lambda expressions
20
double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max();
Pipeline being built Single pass!
Lambda expressions
20
double highestScore = students .filter(Student s -> s.getGradYear() == 2011) .map(Student s -> s.getScore()) .max();
Lambda expressions
21
double highestScore = students .findAll { it.gradYear == 2011 } .collect { it.score} .max()
Lambda expressions
21
double highestScore = students .findAll { it.gradYear == 2011 } .collect { it.score} .max()
In Groovy, that would be…
Lambda expressions
21
double highestScore = students .findAll { it.gradYear == 2011 } .collect { it.score} .max()
Drawback: intermediary data structures Unless you use iterator variants
Lambda expressions
21
double highestScore = students .findAll { it.gradYear == 2011 } .collect { it.score} .max()
The various lambda syntaxes
22
String name -> name.length() !
(int left, int right) -> left + right !
(String left, String sep, String right) -> { System.out.println(left + sep + right) }
The various lambda syntaxes
22
String name -> name.length() !
(int left, int right) -> left + right !
(String left, String sep, String right) -> { System.out.println(left + sep + right) }
One parameter: no parens Expression on right: no curly
The various lambda syntaxes
22
String name -> name.length() !
(int left, int right) -> left + right !
(String left, String sep, String right) -> { System.out.println(left + sep + right) }
Parentheses required for more that one parameter
The various lambda syntaxes
22
String name -> name.length() !
(int left, int right) -> left + right !
(String left, String sep, String right) -> { System.out.println(left + sep + right) }
Statements require curly braces Return keyword required if non-void returning
The various lambda syntaxes
22
String name -> name.length() !
(int left, int right) -> left + right !
(String left, String sep, String right) -> { System.out.println(left + sep + right) }
The various lambda syntaxes
23
name -> name.length() !
(left, right) -> left + right !
(left, sep, right) -> { System.out.println(left + sep + right) }
The various lambda syntaxes
23
name -> name.length() !
(left, right) -> left + right !
(left, sep, right) -> { System.out.println(left + sep + right) }
Clever type inference can help get rid of parameter
type declarations
Closures vs lambdas
24
IntStream.range(1, 100).forEach(s -> System.out.println(s)); !Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it));
Closures vs lambdas
24
IntStream.range(1, 100).forEach(s -> System.out.println(s)); !Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it));
IntStream.range(1, 100).forEach { println it } !Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it }
Closures vs lambdas
24
IntStream.range(1, 100).forEach(s -> System.out.println(s)); !Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it));
IntStream.range(1, 100).forEach { println it } !Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it }
Use Groovy closures wherever you pass lambdas in Java 8
Closures vs lambdas
24
IntStream.range(1, 100).forEach(s -> System.out.println(s)); !Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it));
IntStream.range(1, 100).forEach { println it } !Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it }
Groovy coerces to SAM types (Single Abstract Method)
Closures vs lambdas
24
IntStream.range(1, 100).forEach(s -> System.out.println(s)); !Files.lines(Paths.get('README.adoc')) .map(it -> it.toUpperCase()) .forEach(it -> System.out.println(it));
IntStream.range(1, 100).forEach { println it } !Files.lines(Paths.get('README.adoc')) .map { it.toUpperCase() } .forEach { println it }
Beyond: Closure default parameters
25
def mult = { int a, int b = 10 -> a * b } !
assert mult(2, 3) == 6 assert mult(5) == 50
Beyond: Closure default parameters
25
def mult = { int a, int b = 10 -> a * b } !
assert mult(2, 3) == 6 assert mult(5) == 50
Default value if the parameter is not specified
Beyond: Duck typing polymorphism
26
def adder = { a, b -> a + b } !
assert adder(100, 200) == 300 assert adder('X', 'Y') == 'XY'
Beyond: Duck typing polymorphism
26
def adder = { a, b -> a + b } !
assert adder(100, 200) == 300 assert adder('X', 'Y') == 'XY'
Works both for numbers and for strings
Builders
BuildersWhat would Java lambda
builders look like?
BuildersWhat would Java lambda
builders look like?
Aren’t Groovy builders more powerful?
Lambda-based builders uglier and less powerful
28
MarkupBuilder pom = new XmlMarkupBuilder() pom.el("modelVersion", "4.0.0"); pom.el("groupId", "com.github"); pom.el("artifactId", "lambda-builder"); pom.el("version", "1.0-SNAPSHOT"); pom.el("dependencies", () -> { pom.el("dependency", () -> { pom.el("groupId", "junit"); pom.el("artifactId", "junit"); pom.elx("version", "4.11"); }); pom.el("dependency", () -> { pom.el("groupId", "commons-beanutils"); pom.el("artifactId", "commons-beanutils"); pom.elx("version", "1.7.0"); }); });
Lambda-based builders uglier and less powerful
28
MarkupBuilder pom = new XmlMarkupBuilder() pom.el("modelVersion", "4.0.0"); pom.el("groupId", "com.github"); pom.el("artifactId", "lambda-builder"); pom.el("version", "1.0-SNAPSHOT"); pom.el("dependencies", () -> { pom.el("dependency", () -> { pom.el("groupId", "junit"); pom.el("artifactId", "junit"); pom.elx("version", "4.11"); }); pom.el("dependency", () -> { pom.el("groupId", "commons-beanutils"); pom.el("artifactId", "commons-beanutils"); pom.elx("version", "1.7.0"); }); });
Repeated « pom »: No delegate like in Groovy’s closures
Lambda-based builders uglier and less powerful
28
MarkupBuilder pom = new XmlMarkupBuilder() pom.el("modelVersion", "4.0.0"); pom.el("groupId", "com.github"); pom.el("artifactId", "lambda-builder"); pom.el("version", "1.0-SNAPSHOT"); pom.el("dependencies", () -> { pom.el("dependency", () -> { pom.el("groupId", "junit"); pom.el("artifactId", "junit"); pom.elx("version", "4.11"); }); pom.el("dependency", () -> { pom.el("groupId", "commons-beanutils"); pom.el("artifactId", "commons-beanutils"); pom.elx("version", "1.7.0"); }); });
Zero-arg lamdas not as lean
Lambda-based builders uglier and less powerful
28
MarkupBuilder pom = new XmlMarkupBuilder() pom.el("modelVersion", "4.0.0"); pom.el("groupId", "com.github"); pom.el("artifactId", "lambda-builder"); pom.el("version", "1.0-SNAPSHOT"); pom.el("dependencies", () -> { pom.el("dependency", () -> { pom.el("groupId", "junit"); pom.el("artifactId", "junit"); pom.elx("version", "4.11"); }); pom.el("dependency", () -> { pom.el("groupId", "commons-beanutils"); pom.el("artifactId", "commons-beanutils"); pom.elx("version", "1.7.0"); }); });
Generic method + string: No dynamic
method
Neater Groovy builder!
29
def pom = new PomBuilder().project { modelVersion "4.0.0" groupId "com.github" artifactId "lambda-builder" version "1.0-SNAPSHOT" dependencies { dependency { groupId "junit" artifactId "junit" version "4.11" } dependency { groupId "commons-beanutils" artifactId "commons-beanutils" version "1.7.0" } } }
Neater Groovy builder!
29
def pom = new PomBuilder().project { modelVersion "4.0.0" groupId "com.github" artifactId "lambda-builder" version "1.0-SNAPSHOT" dependencies { dependency { groupId "junit" artifactId "junit" version "4.11" } dependency { groupId "commons-beanutils" artifactId "commons-beanutils" version "1.7.0" } } }
Aren’t Groovy builders more readable and lean?
To those who said no…
31
Memoization
Closure and method memoization
33
def fib2 = { long n -> if (n < 2) 1 else call(n - 1) + call(n - 2) }.memoize()
Closure and method memoization
33
def fib2 = { long n -> if (n < 2) 1 else call(n - 1) + call(n - 2) }.memoize()
Closures:memoize()
Closure and method memoization
33
@Memoized long fib(long n) { if (n < 2) 1 else fib(n - 1) + fib(n - 2) }
def fib2 = { long n -> if (n < 2) 1 else call(n - 1) + call(n - 2) }.memoize()
Closures:memoize()
Closure and method memoization
33
@Memoized long fib(long n) { if (n < 2) 1 else fib(n - 1) + fib(n - 2) }
def fib2 = { long n -> if (n < 2) 1 else call(n - 1) + call(n - 2) }.memoize()
Closures:memoize()
Methods:memoization AST transformation
Tail recursion
Closure and method tail recursion
35
def fact = { n, accu = 1G -> if (n < 2) accu else fact.trampoline(n - 1, n * accu) }.trampoline()
Closure and method tail recursion
35
def fact = { n, accu = 1G -> if (n < 2) accu else fact.trampoline(n - 1, n * accu) }.trampoline()
Closures:Tail recursion with
trampoline()
Closure and method tail recursion
35
@groovy.transform.TailRecursive def fact(n, accu = 1G) { if (n < 2) accu else fact(n - 1, n * accu) }
def fact = { n, accu = 1G -> if (n < 2) accu else fact.trampoline(n - 1, n * accu) }.trampoline()
Closures:Tail recursion with
trampoline()
Closure and method tail recursion
35
@groovy.transform.TailRecursive def fact(n, accu = 1G) { if (n < 2) accu else fact(n - 1, n * accu) }
def fact = { n, accu = 1G -> if (n < 2) accu else fact.trampoline(n - 1, n * accu) }.trampoline()
Closures:Tail recursion with
trampoline()
Methods:Tail recursion with
@TailRecursivetransformation
Method references
36
button.setOnAction(event -> System.out.println(event));
Method references
36
button.setOnAction(event -> System.out.println(event));
button.setOnAction(System.out::println);
Method references — 3 main cases
37
instance::instanceMethod !
SomeClass::staticMethod !
SomeClass::instanceMethod
Method references — 3 main cases
37
instance::instanceMethod !
SomeClass::staticMethod !
SomeClass::instanceMethod
Not covered by Groovy method
closures yet!
Groovy’s method closure
38
instance.&instanceMethod !
SomeClass.&staticMethod !
SomeClass.&instanceMethod
Groovy’s method closure
38
instance.&instanceMethod !
SomeClass.&staticMethod !
SomeClass.&instanceMethod
Choices:Adopt ::
Deprecate .& Enhance .&
Static methods in interfaces
• You can put static utility methods in interfaces, instead of in companion classes (like « Collections »)
39
public interface Stream<T> { // ... static <T> Stream<T> empty() { return new Stream<T> { ... } } }
Default methods in interfaces
• Define default behavior • possibly to enrich existing interfaces
40
public interface Stream<T> { // ... default Builder<T> add(T t) { ... } }
Groovy Traits
• Like interfaces, but with method bodies • similar to Java 8 interface default methods
• Elegant way to compose behavior • multiple inheritance without the « diamond » problem
• Traits can also be stateful • traits can have properties like normal classes
• Compatible with static typing and static compilation • class methods from traits also visible from Java classes
• Also possible to implement traits at runtime 41
Simple trait
42
trait FlyingAbility { String fly() { "I'm flying!" } } !
class Bird implements FlyingAbility {} def b = new Bird() !
assert b.fly() == "I'm flying!"
Trait with state
43
trait Named { String name } !
class Bird implements Named {} def b = new Bird(name: 'Colibri') !
assert b.name == 'Colibri'
Multiple inheritance
44
trait KiteSurfer { String surf() { 'kite' } } !
trait WebSurfer { String surf() { 'web' } } !
class Person { String name } !
class Hipster extends Person implements KiteSurfer, WebSurfer {} !
def h = new Hipster() assert h.surf() == 'web'
To know all about traits!
45
Rethinking API design with traits by Cédric Champeau Tue 2:30pm / Trinity 3
Annotations on types
Repeating annotations
Repeating annotations
47
@Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }
Repeating annotations
47
@Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }
@Schedule annotation repeated twice
Repeating annotations
47
@Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }
@Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; }
Repeating annotations
47
@Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }
@Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; }
Container annotation for the repeated annotationd
Repeating annotations
47
@Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }
@Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; }
public @interface Schedules { Schedule[] value(); }
Repeating annotations
47
@Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }
@Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; }
public @interface Schedules { Schedule[] value(); }
The container annotation iteself
Repeating annotations
47
@Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }
@Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; }
public @interface Schedules { Schedule[] value(); }
Repeating annotations
47
@Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }
@Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; }
public @interface Schedules { Schedule[] value(); }
Not yet supported
Repeating annotations
47
@Schedule(dayOfMonth = "last") @Schedule(dayOfWeek = "Fri", hour = 23) public void doPeriodicCleanup() { ... }
@Repeatable(Schedules.class) public @interface Schedule { String dayOfMonth() default "first"; String dayOfWeek() default "Mon"; int hour() default 12; }
public @interface Schedules { Schedule[] value(); }
Annotations on types
• Everywhere you can put a type, you can put an annotation
48
@NonNull String name; !email = (@Email String) input; !List<@NonNull String> names; !new @Interned MyObject(); !void monitorTemperature() throws @Critical TemperatureException { ... } !class UnmodifiableList<T> implements @Readonly List<@Readonly T> {...}
Annotations on types
• Everywhere you can put a type, you can put an annotation
48
@NonNull String name; !email = (@Email String) input; !List<@NonNull String> names; !new @Interned MyObject(); !void monitorTemperature() throws @Critical TemperatureException { ... } !class UnmodifiableList<T> implements @Readonly List<@Readonly T> {...}
Not yet supported
Annotations on types
• Everywhere you can put a type, you can put an annotation
48
@NonNull String name; !email = (@Email String) input; !List<@NonNull String> names; !new @Interned MyObject(); !void monitorTemperature() throws @Critical TemperatureException { ... } !class UnmodifiableList<T> implements @Readonly List<@Readonly T> {...}
Imagine the potential for targets for local AST
transformations?
Annotations on types
• Everywhere you can put a type, you can put an annotation
48
@NonNull String name; !email = (@Email String) input; !List<@NonNull String> names; !new @Interned MyObject(); !void monitorTemperature() throws @Critical TemperatureException { ... } !class UnmodifiableList<T> implements @Readonly List<@Readonly T> {...}
Groovy compile-time meta-annotations
49
@Service @Transactional class MyTransactionalService {}
Groovy compile-time meta-annotations
49
@Service @Transactional class MyTransactionalService {}
import groovy.transform.AnnotationCollector !@Service @Transactional @AnnotationCollector public @interface TransactionalService {}
Groovy compile-time meta-annotations
49
import groovy.transform.AnnotationCollector !@Service @Transactional @AnnotationCollector public @interface TransactionalService {}
!@TransactionalService class MyTransactionalService {}
Groovy compile-time meta-annotations
49
import groovy.transform.AnnotationCollector !@Service @Transactional @AnnotationCollector public @interface TransactionalService {}
!@TransactionalService class MyTransactionalService {}
Can handle parameters (even conflicting), or you can create
your own « processor »
Groovy compile-time meta-annotations
49
import groovy.transform.AnnotationCollector !@Service @Transactional @AnnotationCollector public @interface TransactionalService {}
!@TransactionalService class MyTransactionalService {}
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
New Java 8 APIs
Date and
Time API
Date / Time API
52
LocalDate.now(); today.with(TemporalAdjusters.lastDayOfMonth()).minusDays(2); !LocalDate.of(2012, Month.MAY, 14); dateOfBirth.plusYears(1); !LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20); LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));
Date / Time API
52
LocalDate.now(); today.with(TemporalAdjusters.lastDayOfMonth()).minusDays(2); !LocalDate.of(2012, Month.MAY, 14); dateOfBirth.plusYears(1); !LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20); LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));
Groovy could add some operator overloading?
Date / Time API
52
LocalDate.now(); today.with(TemporalAdjusters.lastDayOfMonth()).minusDays(2); !LocalDate.of(2012, Month.MAY, 14); dateOfBirth.plusYears(1); !LocalDate date = LocalDate.of(2000, Month.NOVEMBER, 20); LocalDate nextWed = date.with(TemporalAdjusters.next(DayOfWeek.WEDNESDAY));
Groovy’s date / time handling
53
import static java.util.Calendar.* import groovy.time.* import org.codehaus.groovy.runtime.TimeCategory !def cal = Calendar.instance cal.set(year: 2010, month: JULY, date: 9) !assert FRIDAY == cal[DAY_OF_WEEK] !use (TimeCategory) { 2.years + 3.months + 15.days + 23.minutes + 2.seconds ! 1.week - 1.day new Date() + 6.days ! 3.days.ago new Date() - 3 }
Groovy’s date / time handling
53
import static java.util.Calendar.* import groovy.time.* import org.codehaus.groovy.runtime.TimeCategory !def cal = Calendar.instance cal.set(year: 2010, month: JULY, date: 9) !assert FRIDAY == cal[DAY_OF_WEEK] !use (TimeCategory) { 2.years + 3.months + 15.days + 23.minutes + 2.seconds ! 1.week - 1.day new Date() + 6.days ! 3.days.ago new Date() - 3 }
Groovy could provide the same for Date / Time
Groovy’s date / time handling
53
import static java.util.Calendar.* import groovy.time.* import org.codehaus.groovy.runtime.TimeCategory !def cal = Calendar.instance cal.set(year: 2010, month: JULY, date: 9) !assert FRIDAY == cal[DAY_OF_WEEK] !use (TimeCategory) { 2.years + 3.months + 15.days + 23.minutes + 2.seconds ! 1.week - 1.day new Date() + 6.days ! 3.days.ago new Date() - 3 }
Stream API
map
map
map
map
map
reducereduce reduce
reduce
reduce
map
map
map
map
map
reducereduce reduce
reduce
reduce
Map / filter / reduce explained to
your 6 year old
map
map
map
map
map
reducereduce reduce
reduce
reduce
Stream
56
persons.stream() .filter( p -> p.getAge() < 18 ) .map( p -> p.getName().toUpperCase() ) .sorted() .collect(Collectors.joining(", "));
Groovy’s functional style with the GDK methods
57
persons .findAll { it.getAge() < 18 } .collect { it.name.toUpperCase() } .sort() .joining(", ")
Groovy using streams too!
58
persons.stream() .filter { it.age < 18 } .map { it.name.toUpperCase() } .sorted() .collect(Collectors.joining(", "))
Groovy using streams too!
58
persons.stream() .filter { it.age < 18 } .map { it.name.toUpperCase() } .sorted() .collect(Collectors.joining(", "))
Leveraging closure to « SAM » type coercion
Groovy using streams too!
58
persons.stream() .filter { it.age < 18 } .map { it.name.toUpperCase() } .sorted() .collect(Collectors.joining(", "))
Optional
59
Optional<String> maybeName = Optional.of("Guillaume"); !String result = maybeName.orElse("unknown") !if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); }
Optional
59
Optional<String> maybeName = Optional.of("Guillaume"); !String result = maybeName.orElse("unknown") !if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); }
Wrap something that can be potentially null
Optional
59
Optional<String> maybeName = Optional.of("Guillaume"); !String result = maybeName.orElse("unknown") !if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); }
Optional
59
Optional<String> maybeName = Optional.of("Guillaume"); !String result = maybeName.orElse("unknown") !if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); }
Force handling the null
value
Optional
59
Optional<String> maybeName = Optional.of("Guillaume"); !String result = maybeName.orElse("unknown") !if (maybeName.ifPresent()) { System.out.println(maybeName.get()); } else { System.out.println("unknown"); }
You know you can customize the truth in Groovy?
You know you can customize the truth in Groovy?
Just implement a custom asBoolean() method!
The law of Groovy Truth
61
The law of Groovy Truth
61
Everything that’s null, empty, zero-sized, equal
to zero is false
The law of Groovy Truth
61
Everything that’s null, empty, zero-sized, equal
to zero is false
Otherwise, it’s true!
?:
Groovy truth, null handling, Elvis with Optional
63
def maybeName = Optional.of("Guillaume") !def result = maybeName ?: "unknown" !if (maybeName) { println maybeName.get() } else { println "unknown" } !maybeName?.toUpperCase()
Groovy truth, null handling, Elvis with Optional
63
def maybeName = Optional.of("Guillaume") !def result = maybeName ?: "unknown" !if (maybeName) { println maybeName.get() } else { println "unknown" } !maybeName?.toUpperCase()
Elvis!
Groovy truth, null handling, Elvis with Optional
63
def maybeName = Optional.of("Guillaume") !def result = maybeName ?: "unknown" !if (maybeName) { println maybeName.get() } else { println "unknown" } !maybeName?.toUpperCase()
Groovy Truth!
Groovy truth, null handling, Elvis with Optional
63
def maybeName = Optional.of("Guillaume") !def result = maybeName ?: "unknown" !if (maybeName) { println maybeName.get() } else { println "unknown" } !maybeName?.toUpperCase()
Safe navigation
Groovy truth, null handling, Elvis with Optional
63
def maybeName = Optional.of("Guillaume") !def result = maybeName ?: "unknown" !if (maybeName) { println maybeName.get() } else { println "unknown" } !maybeName?.toUpperCase()
Interesting discussions on the mailing-list
to further enhance usage of
Optional from Groovy
Groovy truth, null handling, Elvis with Optional
63
def maybeName = Optional.of("Guillaume") !def result = maybeName ?: "unknown" !if (maybeName) { println maybeName.get() } else { println "unknown" } !maybeName?.toUpperCase()
Not Yet Implemented
Groovy truth, null handling, Elvis with Optional
63
def maybeName = Optional.of("Guillaume") !def result = maybeName ?: "unknown" !if (maybeName) { println maybeName.get() } else { println "unknown" } !maybeName?.toUpperCase()
Nashorn
Nashorn
Speak German?
Call JavaScript from Groovy with JSR-223
65
def manager = new ScriptEngineManager() def engine = manager.getEngineByName("nashorn") !
assert engine.eval("{} + []") == 0
Call JavaScript from Groovy with JSR-223
65
def manager = new ScriptEngineManager() def engine = manager.getEngineByName("nashorn") !
assert engine.eval("{} + []") == 0
Sane JavaScript logic :-)
GrooScript — http://grooscript.org/
66
JSGradle & Grails plugins available
Examples available with Ratpack, Node.JS…
Turtles all the way down!
Turtles all the way down!
Full Groovy!
Turtles all the way down!
Full Groovy!
Back to front
Turtles all the way down!
Full Groovy!
Back to front
Front to back
Jackson Pollock
JavaFx… !
!
…the new Swing
GroovyFX
70
import javafx.scene.Scene import static groovyx.javafx.GroovyFX.start !def chamber = ["5 Stelle": 108, "Italia.\nBene commune": 340, "Con Monti per l'Italia": 45, "Berlusconi": 124, "others": 4] !start { stage(title: 'Italian chamber of Deputies', width: 1024, height: 700, visible: true) { Scene s = scene { tilePane { barChart(barGap: 10, categoryGap: 20, title: "Italy's election in February 2013") { series(name: 'Chamber (seats)', data: ['5 Stelle', 17.5, 'Monti', 7.8, 'Bene commune', 55, 'Berlusconi', 19.9]) series(name: 'Senate (seats)', data: ['5 Stelle', 17.4, 'Monti', 6.1, 'Bene commune', 39.1, 'Berlusconi', 37.1]) } pieChart(data: chamber, title: "Chamber of Deputies") } } s.stylesheets.add("Chart.css") } }
GroovyFX
70
import javafx.scene.Scene import static groovyx.javafx.GroovyFX.start !def chamber = ["5 Stelle": 108, "Italia.\nBene commune": 340, "Con Monti per l'Italia": 45, "Berlusconi": 124, "others": 4] !start { stage(title: 'Italian chamber of Deputies', width: 1024, height: 700, visible: true) { Scene s = scene { tilePane { barChart(barGap: 10, categoryGap: 20, title: "Italy's election in February 2013") { series(name: 'Chamber (seats)', data: ['5 Stelle', 17.5, 'Monti', 7.8, 'Bene commune', 55, 'Berlusconi', 19.9]) series(name: 'Senate (seats)', data: ['5 Stelle', 17.4, 'Monti', 6.1, 'Bene commune', 39.1, 'Berlusconi', 37.1]) } pieChart(data: chamber, title: "Chamber of Deputies") } } s.stylesheets.add("Chart.css") } }
Groovy builder to the rescue!
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Beyond Java
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Easy scripting
Scripting
73
@Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* !def src = new File('html').toPath() def dst = new File('asciidoc').toPath() !def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } }
<html/>
AsciiDoctor
Ant scripting
74
@Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* !def src = new File('html').toPath() def dst = new File('asciidoc').toPath() !def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text
Ant scripting
74
@Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* !def src = new File('html').toPath() def dst = new File('asciidoc').toPath() !def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text
Use @Grab to download the htmlcleaner library
Ant scripting
74
@Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* !def src = new File('html').toPath() def dst = new File('asciidoc').toPath() !def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text
Ant scripting
74
@Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* !def src = new File('html').toPath() def dst = new File('asciidoc').toPath() !def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text
Setup HtmlCleaner
Ant scripting
74
@Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* !def src = new File('html').toPath() def dst = new File('asciidoc').toPath() !def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text
Ant scripting
74
@Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* !def src = new File('html').toPath() def dst = new File('asciidoc').toPath() !def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text
Recursively find all the files to transform
Ant scripting
74
@Grab('net.sourceforge.htmlcleaner:htmlcleaner:2.4') import org.htmlcleaner.* !def src = new File('html').toPath() def dst = new File('asciidoc').toPath() !def cleaner = new HtmlCleaner() def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text
Ant scripting
75
def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc"
Ant scripting
75
def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc"
Clean the HTML
Ant scripting
75
def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc"
Ant scripting
75
def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc"
Some more clean-up
Ant scripting
75
def props = cleaner.properties props.translateSpecialEntities = false def serializer = new SimpleHtmlSerializer(props) !src.toFile().eachFileRecurse { f -> def relative = src.relativize(f.toPath()) def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc"
Ant scripting
76
def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } }
Ant scripting
76
def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } }
Write the result
Ant scripting
76
def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } }
Ant scripting
76
def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } }
Call an external process to convert and wait for its execution
Ant scripting
76
def target = dst.resolve(relative) if (f.isDirectory()) { target.toFile().mkdir() } else if (f.name.endsWith('.html')) { def tmpHtml = File.createTempFile('clean', 'html') println "Converting $relative" def result = cleaner.clean(f) result.traverse({ tagNode, htmlNode -> tagNode?.attributes?.remove 'class' if ('td' == tagNode?.name || 'th'==tagNode?.name) { tagNode.name='td' String txt = tagNode.text tagNode.removeAllChildren() tagNode.insertChild(0, new ContentNode(txt)) } true } as TagNodeVisitor) serializer.writeToFile( result, tmpHtml.absolutePath, "utf-8" ) "pandoc -f html -t asciidoc -R -S --normalize -s $tmpHtml -o ${target}.adoc" .execute().waitFor() tmpHtml.delete() } }
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Android
Android support
• You can use Groovy to code Android apps! • use Groovy 2.4.0-beta-1+ • prefer @CompileStatic !
• Two great posts to get started: • http://melix.github.io/blog/2014/06/grooid.html • http://melix.github.io/blog/2014/06/grooid2.html
78
New York Times — Getting Groovy with Android
79
What does NYT likes about Groovy on Android?
• No Java 8, no lambda on Android…
80
Func0 func = new Func0<string>() { @Override public String call() { return "my content"; } }; Async.start(func);
What does NYT likes about Groovy on Android?
• No Java 8, no lambda on Android…
81
!
!
!
!
!
!
Async.start { "my content" }
What does NYT likes about Groovy on Android?
• No Java 8, no lambda on Android…
81
!
!
!
!
!
!
Async.start { "my content" }
Good bye annonymous inner classes!
What does NYT likes about Groovy on Android?
!
• Groovy code more concise and more readable !
• but just as type-safe as needed!(with @TypeChecked) !
• but just as fast as needed!(with @CompileStatic)
82
Android support
83
Android support
83
Android support
83
Source code available:https://github.com/melix/gr8confagenda
To know all about Android support
84
Groovy & Android, a winning pair? by Cédric Champeau Thu 12:45pm / Trinity 3
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
A rich Groovy ecosystem
Grails — web framework
86
@Grab("com.h2database:h2:1.3.173") import grails.persistence.* !
@Entity @Resource(uri='/books') class Book { String title }
Grails — web framework
86
@Grab("com.h2database:h2:1.3.173") import grails.persistence.* !
@Entity @Resource(uri='/books') class Book { String title }
One class, one command, and you’ve got a full REST
CRUD application!
Ratpack — web framework
87
@GrabResolver("https://oss.jfrog.org/artifactory/repo") @Grab("org.ratpack-framework:ratpack-groovy:0.9.8") import static org.ratpackframework.groovy.RatpackScript.ratpack import static org.ratpackframework.groovy.Template.groovyTemplate !ratpack { handlers { get { response.send "Welcome!" } ! get("date") { render groovyTemplate("date.html") } ! assets "public" } }
Ratpack — web framework
87
@GrabResolver("https://oss.jfrog.org/artifactory/repo") @Grab("org.ratpack-framework:ratpack-groovy:0.9.8") import static org.ratpackframework.groovy.RatpackScript.ratpack import static org.ratpackframework.groovy.Template.groovyTemplate !ratpack { handlers { get { response.send "Welcome!" } ! get("date") { render groovyTemplate("date.html") } ! assets "public" } }
Lightweight Netty-based web application toolkit
@ArtifactProviderFor(GriffonModel) class ConsoleModel { String scriptSource @Observable Object scriptResult @Observable boolean enabled = true }
Griffon — rich desktop applications
88
@ArtifactProviderFor(GriffonModel) class ConsoleModel { String scriptSource @Observable Object scriptResult @Observable boolean enabled = true }
Griffon — rich desktop applications
88
Model
@ArtifactProviderFor(GriffonModel) class ConsoleModel { String scriptSource @Observable Object scriptResult @Observable boolean enabled = true }
Griffon — rich desktop applications
88
Model
@ArtifactProviderFor(GriffonController) class ConsoleController { def model ! @Inject Evaluator evaluator ! void executeScript() { model.enabled = false def result try { result = evaluator.evaluate(model.scriptSource) } finally { model.enabled = true model.scriptResult = result } } }
Controller
@ArtifactProviderFor(GriffonModel) class ConsoleModel { String scriptSource @Observable Object scriptResult @Observable boolean enabled = true }
Griffon — rich desktop applications
88
Model
@ArtifactProviderFor(GriffonController) class ConsoleController { def model ! @Inject Evaluator evaluator ! void executeScript() { model.enabled = false def result try { result = evaluator.evaluate(model.scriptSource) } finally { model.enabled = true model.scriptResult = result } } }
Controller
application(title: application.configuration['application.title'], pack: true, locationByPlatform: true, id: 'mainWindow', iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/griffon-icon-48x48.png').image, imageIcon('/griffon-icon-32x32.png').image, imageIcon('/griffon-icon-16x16.png').image]) { panel(border: emptyBorder(6)) { borderLayout() scrollPane(constraints: CENTER) { textArea(text: bind(target: model, 'scriptSource'), enabled: bind { model.enabled }, columns: 40, rows: 10) } ! hbox(constraints: SOUTH) { button(executeScriptAction) hstrut(5) label('Result:') hstrut(5) textField(editable: false, text: bind { model.scriptResult }) } } }
View
Spock — unit testing & specification
89
@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 || 7 0 | 0 || 0 } }
Spock — unit testing & specification
89
@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 || 7 0 | 0 || 0 } }
Readable & concise expectations
Spock — unit testing & specification
89
@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 || 7 0 | 0 || 0 } }
Readable & concise expectations
Awesome data-driven tests with a wiki-like notation
Geb — browser automation
90
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" }
Geb — browser automation
90
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" }
Drive your browser
Geb — browser automation
90
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" }
Drive your browser
JQuery-like selectors
Geb — browser automation
90
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" }
Drive your browser
JQuery-like selectors
Fill & submit forms
Gradle — build automation
91
apply plugin: 'java' apply plugin: 'eclipse' !sourceCompatibility = 1.5 version = '1.0' jar { manifest { attributes 'Implementation-Title': 'Gradle Quickstart' } } !repositories { mavenCentral() } !dependencies { compile ‘commons-collections:commons-collections:3.2’ testCompile 'junit:junit:4.+' } !uploadArchives { repositories { flatDir { dirs 'repos' } } }
Gradle — build automation
91
apply plugin: 'java' apply plugin: 'eclipse' !sourceCompatibility = 1.5 version = '1.0' jar { manifest { attributes 'Implementation-Title': 'Gradle Quickstart' } } !repositories { mavenCentral() } !dependencies { compile ‘commons-collections:commons-collections:3.2’ testCompile 'junit:junit:4.+' } !uploadArchives { repositories { flatDir { dirs 'repos' } } }
Powerful and readable DSL for automating your builds
and deployments
GPars — concurrency / parallelism / async / …
92
import static groovyx.gpars.actor.Actors.actor !def decryptor = actor { loop { react { message -> if (message instanceof String) reply message.reverse() else stop() } } } !def console = actor { decryptor.send 'lellarap si yvoorG' react { println 'Decrypted message: ${it}' decryptor.send false } } ![decryptor, console]*.join()
GPars — concurrency / parallelism / async / …
92
import static groovyx.gpars.actor.Actors.actor !def decryptor = actor { loop { react { message -> if (message instanceof String) reply message.reverse() else stop() } } } !def console = actor { decryptor.send 'lellarap si yvoorG' react { println 'Decrypted message: ${it}' decryptor.send false } } ![decryptor, console]*.join()
Actors…
GPars — concurrency / parallelism / async / …
92
import static groovyx.gpars.actor.Actors.actor !def decryptor = actor { loop { react { message -> if (message instanceof String) reply message.reverse() else stop() } } } !def console = actor { decryptor.send 'lellarap si yvoorG' react { println 'Decrypted message: ${it}' decryptor.send false } } ![decryptor, console]*.join()
Actors…
But also:Dataflow concurrency, CSP, agents, concurrent collection processing,
fork / join, composable async functions, STM
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Groovy modules
Groovy modules
• Ant scripting • JMX • JSON • Servlet • Swing • SQL • Templating • Testing • XML
94
Groovy modules
• Ant scripting • JMX • JSON • Servlet • Swing • SQL • Templating • Testing • XML
95
Ant + XML
96
def writer = new StringWriter() def mkp = new MarkupBuilder(writer) !mkp.html { head { title 'Build notification' } body { p 'Your build was successful' } } !new AntBuilder().mail(mailhost: 'localhost', messagemimetype: 'text/html', subject: 'Build successful') { ! from address: '[email protected]' to address: '[email protected]' message writer attchments { fileset(dir: 'dist') { include name: '**/logs*.txt' } } }
Ant + XML
96
def writer = new StringWriter() def mkp = new MarkupBuilder(writer) !mkp.html { head { title 'Build notification' } body { p 'Your build was successful' } } !new AntBuilder().mail(mailhost: 'localhost', messagemimetype: 'text/html', subject: 'Build successful') { ! from address: '[email protected]' to address: '[email protected]' message writer attchments { fileset(dir: 'dist') { include name: '**/logs*.txt' } } }
Generate some HTML with the XML support
Ant + XML
96
def writer = new StringWriter() def mkp = new MarkupBuilder(writer) !mkp.html { head { title 'Build notification' } body { p 'Your build was successful' } } !new AntBuilder().mail(mailhost: 'localhost', messagemimetype: 'text/html', subject: 'Build successful') { ! from address: '[email protected]' to address: '[email protected]' message writer attchments { fileset(dir: 'dist') { include name: '**/logs*.txt' } } }
Generate some HTML with the XML support
Use the Ant builder and the mail task
Ant + XML
96
def writer = new StringWriter() def mkp = new MarkupBuilder(writer) !mkp.html { head { title 'Build notification' } body { p 'Your build was successful' } } !new AntBuilder().mail(mailhost: 'localhost', messagemimetype: 'text/html', subject: 'Build successful') { ! from address: '[email protected]' to address: '[email protected]' message writer attchments { fileset(dir: 'dist') { include name: '**/logs*.txt' } } }
Generate some HTML with the XML support
Use the Ant builder and the mail task
Use the Ant’s fileset
JSON support — creating JSON
97
import groovy.json.* !def json = new JsonBuilder() json.person { name 'Guillaume' age 37 daughters 'Marion', 'Erine' address { street '1 Main Street' zip 75001 city 'Paris' } }
JSON support — creating JSON
97
import groovy.json.* !def json = new JsonBuilder() json.person { name 'Guillaume' age 37 daughters 'Marion', 'Erine' address { street '1 Main Street' zip 75001 city 'Paris' } }
{ "person": { "name": "Guillaume", "age": 37, "daughters": [ "Marion", "Erine" ], "address": { "street": "1 Main Street", "zip": 75001, "city": "Paris" } } }
JSON support — parsing JSON
98
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'
JSON support — parsing JSON
98
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'
The power of a dynamic language!
Template module — new markup template engine
• Based on the principles of Groovy’s « builders » • and particularly the MarkupBuilder class
for generating arbitrary XML / HTML payloads !
• Compiled statically for fast template rendering !
• Internationalization aware • provide the desired Locale in the configuration object • usual suffix notation template_fr_FR.tpl !
• Custom base template class • ability to provide reusable methods across your templates 99
Template module — new markup template engine
• Based on the principles of Groovy’s « builders » • and particularly the MarkupBuilder class
for generating arbitrary XML / HTML payloads !
• Compiled statically for fast template rendering !
• Internationalization aware • provide the desired Locale in the configuration object • usual suffix notation template_fr_FR.tpl !
• Custom base template class • ability to provide reusable methods across your templates 99
Spring Boot approved
Markup template engine — the idea
100
cars { cars.each { car(make: it.make, name: it.name) } }
Markup template engine — the idea
100
cars { cars.each { car(make: it.make, name: it.name) } }
Your template
Markup template engine — the idea
100
cars { cars.each { car(make: it.make, name: it.name) } }
model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]]
Markup template engine — the idea
100
cars { cars.each { car(make: it.make, name: it.name) } }
model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]]
Feed a model into your template
Markup template engine — the idea
100
cars { cars.each { car(make: it.make, name: it.name) } }
model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]]
<cars> <car make='Peugeot' name='508'/> <car make='Toyota' name='Prius'/> </cars>
Markup template engine — the idea
100
cars { cars.each { car(make: it.make, name: it.name) } }
model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]]
<cars> <car make='Peugeot' name='508'/> <car make='Toyota' name='Prius'/> </cars>
Generate the XML output
Markup template engine — the idea
100
cars { cars.each { car(make: it.make, name: it.name) } }
model = [cars: [ new Car(make: 'Peugeot', name: '508'), new Car(make: 'Toyota', name: 'Prius’) ]]
<cars> <car make='Peugeot' name='508'/> <car make='Toyota' name='Prius'/> </cars>
Markup template engine — in action
101
import groovy.text.markup.* !def config = new TemplateConfiguration() def engine = new MarkupTemplateEngine(config) def tmpl = engine.createTemplate(''' p("Hello ${model.name}") ''') def model = [name: 'World'] System.out << tmpl.make(model)
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Summary
Back to our original question…
103
Do we still need Groovy now that we
have Java 8?
Longer answer: Yes, because…
• You can benefit from Java 8 in Groovy
105
Synergy the whole is greater than the sum of the parts
Longer answer: Yes, because…
• Groovy goes beyond what Java 8 offers
106
BeyondGroovy always
tries to add something to the table
Questions still open: Syntax support for…
• lambdas • not necessarily,
(confusing / duplication) !
• method references • enhance method closures • or replace them with
method references !
• interface default methods • yes: traits methods aren’t
default methods
• repeated annotations • yes: not urgent • but less boilerplate is good !
• annotations on type • yes: opens up new
possibilities for targets of local AST transformation
!
• interface static methods • yes: for Java compatibility
107
Further possible API enhancements
• Make Optional Groovy-friendly • with regards to Groovy Truth • accessing the wrapped value !
• More Groovy methods for… • NIO • Streams • Date / time !
• Operator overloading for Date / time • for arithmetics on instants and durations
108
Further possible API enhancements
• Make Optional Groovy-friendly • with regards to Groovy Truth • accessing the wrapped value !
• More Groovy methods for… • NIO • Streams • Date / time !
• Operator overloading for Date / time • for arithmetics on instants and durations
108
Community feedback &
contributions welcome!
More Java 8 coverage
109
Java 8 language capabilities by V. Subramaniam Thu 10:30pm / D.Ball.G
More Java 8 coverage
110
How to get Groovy with Java 8 by Peter Ledbrook Thu 10:30pm / Trinity 3
Java 8 in Action
!
• By Urma, Fusco and Mycrof • Plublished by Manning • http://www.manning.com/urma/
111
© 2014 SpringOne 2GX. All rights reserved. Do not distribute without permission.
Q & A
Image credits
• Question mark • http://www.mynamesnotmommy.com/wp-content/uploads/2013/05/question-mark.png
• Get out! • http://static.comicvine.com/uploads/original/11/117995/3772037-0075368930-27616.jpg
• Yes • http://soloprpro.com/wp-content/uploads/2013/08/yes.jpg
• Synergy • http://www.wildblueberries.com/wp-content/uploads/blogger/_YhH8fDK5-kU/S_5kYWwgAbI/AAAAAAAAAKU/JQ_-ISfT9KY/
s1600/teamwork.jpg • Buzz Lightyear
• http://www.disneypictures.net/data/media/202/Buzz_Lightyear_hd.jpg • Start wars spaceship
• http://swc.fs2downloads.com/media/screenshots/Support_Trans/Shuttle/lambda003.jpg • Lambda character
• http://lambda.ninjackaton.ninja-squad.com/images/lambda.png • Man clock
• http://3.bp.blogspot.com/-7hLQ9tnmA84/TuTIoLRLMTI/AAAAAAAABWM/g7ahyLCRjJQ/s1600/Harold+Lloyd+Safety+Last.jpg • Stream
• http://wallpaperswide.com/forest_stream-wallpapers.html • Nashorn (rhino)
• http://2010sdafrika.files.wordpress.com/2012/07/hi_257587-nashorn-c-naturepl-com-mark-carwardine-wwf-canon.jpg 114
Image credits
• Brain • http://www.semel.ucla.edu/sites/all/files/users/user-412/dreamstime_xxl_17754591%20(2).jpg
• Many thanks • http://www.trys.ie/wp-content/uploads/2013/06/many-thanks.jpg
• Swing • http://makemesomethingspecial.co.uk/wp-content/uploads/2012/10/Solid-Oak-Handmade-Spliced-Swing-With-Personalised-
Engraving-10867.jpg • Disclaimer
• http://3.bp.blogspot.com/-RGnBpjXTCQA/Tj2h_JsLigI/AAAAAAAABbg/AB5ZZYzuE5w/s1600/disclaimer.jpg • Hammer / justice / truth
• http://www.bombayrealty.in/images/disclaimer.jpg • Law
• http://www.permanentmakeupbymv.com/wp-content/uploads/2014/07/law-2.jpg • Glasses / readable
• http://a.fastcompany.net/multisite_files/coexist/imagecache/1280/poster/2013/02/1681393-poster-1280-responsive-eye-chart.jpg • We need you
• http://www.bostonbreakerssoccer.com/imgs/ABOUT/volopps/we%20need%20you.JPG
115
Image credits
• Jackson Pollock • http://www.ibiblio.org/wm/paint/auth/pollock/pollock.number-8.jpg
• Turtles • http://33.media.tumblr.com/77095e3a37acb2272133c405b1f7ba54/tumblr_myfydjNgXJ1s20kxvo1_1280.jpg
• Annotations • http://3.bp.blogspot.com/-f94n9BHko_s/T35ELs7nYvI/AAAAAAAAAKg/qe06LRPH9U4/s1600/IMG_1721.JPG
• Builders • http://detroittraining.com/wp-content/uploads/Construction-Women2.jpg
• Sandwich ingredients • http://www.fromages-de-terroirs.com/IMG/MEULE_DETOUREE_72_DPI_-2.jpg • http://eboutique-hebergement.orange-business.com/WebRoot/Orange/Shops/Madeinalsace/4B02/6320/943E/DB4F/DDBF/
0A0A/33E8/4BC8/iStock_000008775559XSmall.jpg • http://www.cmonprimeur.fr/images/25834-vegetable-pictures%5B1%5D.jpg • http://1.bp.blogspot.com/-27UoqYeYtY4/T4PAr4zGSnI/AAAAAAAAA3s/lleE-NOh0LE/s1600/IMG_1528.JPG • http://www.growingpatch.com/wp-content/uploads/2014/05/tomato-9.jpg • http://vitaminsandhealthsupplements.com/wp-content/uploads/2013/08/fresh-sliced-tomato.jpg • http://2.bp.blogspot.com/-3pPmRhBHv9s/T3D-EtLlN1I/AAAAAAAAAgg/Y5oTM84nGb8/s1600/cochon+2.jpg • http://www.salamarket.fr/wp-content/uploads/steak-hach%C3%A9.jpg • http://www.quinzanisbakery.com/images/bread-vienna.jpg • http://www.saucissonvaudois.qc.ca/images/produits/Jambon_blanc.jpg • http://www.audion.com/system/public/categories/125/images/bread-sandwich.jpg 116