This chapter covers the semantic of the Groovy programming language.
Statements (TBD)
Variable definition (TBD)
Variable assignment (TBD)
Multiple assignment (TBD)
Control structures (TBD)
Conditional structures (TBD)
if / else (TBD)
switch / case (TBD)
Looping structures (TBD)
Classic for loop (TBD)
for in loop (TBD)
while loop (TBD)
Exception handling (TBD)
try / catch / finally (TBD)
Multi-catch (TBD)
Power assertion (TBD)
Labeled statements (TBD)
Expressions (TBD)
GPath expressions (TBD)
Promotion and coercion (TBD)
Number promotion (TBD)
Closure to type coercion
Assigning a closure to a SAM type
A SAM type is a type which defines a single abstract method. This includes:
interface Predicate<T> {
boolean accept(T obj)
}
abstract class Greeter {
abstract String getName()
void greet() {
println "Hello, $name"
}
}
Any closure can be converted into a SAM type using the as
operator:
Predicate filter = { it.contains 'G' } as Predicate
assert filter.accept('Groovy') == true
Greeter greeter = { 'Groovy' } as Greeter
greeter.greet()
However, the as Type
expression is optional since Groovy 2.2.0. You can omit it and simply write:
Predicate filter = { it.contains 'G' }
assert filter.accept('Groovy') == true
Greeter greeter = { 'Groovy' }
greeter.greet()
which means you are also allowed to use method pointers, as shown in the following example:
boolean doFilter(String s) { s.contains('G') }
Predicate filter = this.&doFilter
assert filter.accept('Groovy') == true
Greeter greeter = GroovySystem.&getVersion
greeter.greet()
Calling a method accepting a SAM type with a closure
The second and probably more important use case for closure to SAM type coercion is calling a method which accepts a SAM type. Imagine the following method:
public <T> List<T> filter(List<T> source, Predicate<T> predicate) {
source.findAll { predicate.accept(it) }
}
Then you can call it with a closure, without having to create an explicit implementation of the interface:
assert filter(['Java','Groovy'], { it.contains 'G'} as Predicate) == ['Groovy']
But since Groovy 2.2.0, you are also able to omit the explicit coercion and call the method as if it used a closure:
assert filter(['Java','Groovy']) { it.contains 'G'} == ['Groovy']
As you can see, this has the advantage of letting you use the closure syntax for method calls, that is to say put the closure outside of the parenthesis, improving the readability of your code.
Closure to arbitrary type coercion
In addition to SAM types, a closure can be coerced to any type and in particular interfaces. Let’s define the following interface:
interface FooBar {
int foo()
void bar()
}
You can coerce a closure into the interface using the as
keyword:
def impl = { println 'ok'; 123 } as FooBar
This produces a class for which all methods are implemented using the closure:
assert impl.foo() == 123
impl.bar()
But it is also possible to coerce a closure to any class. For example, we can replace the interface
that we defined
with class
without changing the assertions:
class FooBar {
int foo() { 1 }
void bar() { println 'bar' }
}
def impl = { println 'ok'; 123 } as FooBar
assert impl.foo() == 123
impl.bar()
Map to type coercion
Usually using a single closure to implement an interface or a class with multiple methods is not the way to go. As an
alternative, Groovy allows you to coerce a map into an interface or a class. In that case, keys of the map are
interpreted as method names, while the values are the method implementation. The following example illustrates the
coercion of a map into an Iterator
:
def map
map = [
i: 10,
hasNext: { map.i > 0 },
next: { map.i-- },
]
def iter = map as Iterator
Of course this is a rather contrived example, but illustrates the concept. You only need to implement those methods
that are actually called, but if a method is called that doesn’t exist in the map a MissingMethodException
or an
UnsupportedOperationException
is thrown, depending on the arguments passed to the call,
as in the following example:
interface X {
void f()
void g(int n)
void h(String s, int n)
}
x = [ f: {println "f called"} ] as X
x.f() // method exists
x.g() // MissingMethodException here
x.g(5) // UnsupportedOperationException here
The type of the exception depends on the call itself:
-
MissingMethodException
if the arguments of the call do not match those from the interface/class -
UnsupportedOperationException
if the arguments of the call match one of the overloaded methods of the interface/class
String to enum coercion
Groovy allows transparent String
(or GString
) to enum values coercion. Imagine you define the following enum:
enum State {
up,
down
}
then you can assign a string to the enum without having to use an explicit as
coercion:
State st = 'up'
assert st == State.up
It is also possible to use a GString
as the value:
def val = "up"
State st = "${val}"
assert st == State.up
However, this would throw a runtime error (IllegalArgumentException
):
State st = 'not an enum value'
Note that it is also possible to use implicit coercion in switch statements:
State switchState(State st) {
switch (st) {
case 'up':
return State.down // explicit constant
case 'down':
return 'up' // implicit coercion for return types
}
}
in particular, see how the case
use string constants. But if you call a method that uses an enum with a String
argument, you still have to use an explicit as
coercion:
assert switchState('up' as State) == State.down
assert switchState(State.down) == State.up
Custom type coercion
It is possible for a class to define custom coercion strategies by implementing the asType
method. Custom coercion
is invoked using the as
operator and is never implicit. As an example,
imagine you defined two classes, Polar
and Cartesian
, like in the following example:
class Polar {
double r
double phi
}
class Cartesian {
double x
double y
}
And that you want to convert from polar coordinates to cartesian coordinates. One way of doing this is to define
the asType
method in the Polar
class:
def asType(Class target) {
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}
which allows you to use the as
coercion operator:
def sigma = 1E-16
def polar = new Polar(r:1.0,phi:PI/2)
def cartesian = polar as Cartesian
assert abs(cartesian.x-sigma) < sigma
Putting it all together, the Polar
class looks like this:
class Polar {
double r
double phi
def asType(Class target) {
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}
}
but it is also possible to define asType
outside of the Polar
class, which can be practical if you want to define
custom coercion strategies for "closed" classes or classes for which you don’t own the source code, for example using
a metaclass:
Polar.metaClass.asType = { Class target ->
if (Cartesian==target) {
return new Cartesian(x: r*cos(phi), y: r*sin(phi))
}
}
Class literals vs variables and the as operator
Using the as
keyword is only possible if you have a static reference to a class, like in the following code:
interface Greeter {
void greet()
}
def greeter = { println 'Hello, Groovy!' } as Greeter // Greeter is known statically
greeter.greet()
But what if you get the class by reflection, for example by calling Class.forName
?
Class clazz = Class.forName('Greeter')
Trying to use the reference to the class with the as
keyword would fail:
greeter = { println 'Hello, Groovy!' } as clazz
// throws:
// unable to resolve class clazz
// @ line 9, column 40.
// greeter = { println 'Hello, Groovy!' } as clazz
It is failing because the as
keyword only works with class literals. Instead, you need to call the asType
method:
greeter = { println 'Hello, Groovy!' }.asType(clazz)
greeter.greet()