Search

Dark theme | Light theme

November 19, 2013

Groovy Goodness: Implicit Closure Coercion

Groovy is awesome and closures are cool. In Java 8 we will see lambdas which are similar like closures. A Java lambda will be automatically converted to an interface type if the interface contains only one single abstract method (SAM). In Groovy we can convert a closure into an interface type by using the as keyword. But since Groovy 2.2 we don't have to use the as keyword, but let Groovy, like Java lambdas, convert a closure implicitly to an interface type. This works for interfaces with a single method, but Groovy wouldn't be Groovy if it didn't work for abstract classes with one abstract method. So, yes, it also works for abstract classes with a single abstract method.

In the following code we define a new class User and make the class send out PropertyChangeEvent events when a property value changes by applying the @Bindable AST transformation to the class. The @Bindable annotation will make sure all code is generated in the class file to inform classes implementing the PropertyChangeListener interface of changes in property values. The nice thing about the PropertyChangeListener interface is that is contains a single method and applies to Single Abstract Method (SAM). In the sample we use a closure that is automatically converted to a PropertyChangeListener type.

import groovy.beans.*
import java.beans.*

@Bindable
class User {
    String name, email
}

def u = new User(name: 'mrhaki', email: 'mrhaki@mrhaki.com')

// Since Groovy 2.2 we don't have to use the as keyword like
// { ... } as PropertyChangeListener,
// but we can rely on implicit coercion.
u.addPropertyChangeListener { event ->
    println "Changed property $event.propertyName from $event.oldValue to $event.newValue"
}

u.name = 'Hubert A. Klein Ikkink'
// Output: Changed property name from mrhaki to Hubert A. Klein Ikkink

u.email = 'hubert@mrhaki.com'
// Output: Changed property email from mrhaki@mrhaki.com to hubert@mrhaki.com

In the next sample we define an abstract class ChangedProperty with a single abstract method (SAM) as listener implementation. When we define an instance we can use a closure and it will automatically be the implementation of the abstract method:

import groovy.beans.*
import java.beans.*

@Bindable
class User {
    String name, email
}

def u = new User(name: 'mrhaki', email: 'mrhaki@mrhaki.com')

// Assign closure as implementation of single abstract method onPropertyChange
// in abstract class ChangedProperty.
ChangedProperty changedProperty = { event -> 
    println "Changed property $event.propertyName from $event.oldValue to $event.newValue" 
}
u.addPropertyChangeListener changedProperty

u.name = 'Hubert A. Klein Ikkink'
// Output: Changed property name from mrhaki to Hubert A. Klein Ikkink

u.email = 'hubert@mrhaki.com'
// Output: Changed property email from mrhaki@mrhaki.com to hubert@mrhaki.com

assert changedProperty.events.size() == 2
assert changedProperty.events.first().oldValue == 'mrhaki'
assert changedProperty.events.first().newValue == 'Hubert A. Klein Ikkink'

abstract class ChangedProperty implements PropertyChangeListener {

    List<PropertyChangeEvent> events = []

    void propertyChange(PropertyChangeEvent event) {
        events << event
        onPropertyChange(event);
    }
    
    abstract void onPropertyChange(PropertyChangeEvent event)

}

Code written with Groovy 2.2.