Java.next: Extension without inheritance, Part 3

Groovy metaprogramming gives you easy solutions to common problems

The Java.next languages — Groovy, Scala, and Clojure — remedy the Java™ language's extension limitations in numerous ways. This Java.next installment covers the astounding extension capabilities that are available through Groovy's metaprogramming facilities.

Neal Ford (nford@thoughtworks.com), Director / Software Architect / Meme Wrangler, ThoughtWorks

Photo of Neal fordNeal Ford is Director, Software Architect, and Meme Wrangler at ThoughtWorks, a global IT consultancy. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, and video/DVD presentations, and he is the author or editor of books spanning a variety of technologies, including the most recent Presentation Patterns. He focuses on designing and building large-scale enterprise applications. He is also an internationally acclaimed speaker at developer conferences worldwide. Check out his website.



03 September 2013

Also available in Chinese Russian Japanese

About this series

The Java legacy will be the platform, not the language. More than 200 languages run on the JVM, and it's inevitable that one of them will eventually supplant the Java language as the best way to program the JVM. This series explores three next-generation JVM languages: Groovy, Scala, and Clojure, comparing and contrasting new capabilities and paradigms, to provide Java developers a glimpse into their own near future.

The preceding two Java.next installments investigate some of the myriad ways in which the Java.next languages make it possible to extend existing classes and other artifacts. In this installment, I continue that exploration with a close look at Groovy metaprogramming techniques that enable extension in a wide variety of contexts.

In "Extension without inheritance, Part 1," I touched incidentally on some Groovy metaprogramming features when I discussed category classes and the ExpandoMetaClass as mechanisms that "bolt on" new behavior to existing classes. The metaprogramming features in Groovy also go much deeper: They make integration with Java code easier and help you to perform common tasks with less ceremony than the Java language requires.

Interface coercion

Interfaces are the common semantic reuse mechanism in the Java language. Other languages that try to integrate with Java code cleanly should, then, offer easy ways to reify interfaces. In Groovy, classes can extend interfaces in the traditional Java way. But Groovy also makes it easy to coerce closures and maps into interface instances when that's convenient.

Single-method coercion

The Java code example in Listing 1 uses the FilenameFilter interface to locate files:

Listing 1. Listing files by using the FilenameFilter interface in Java
import java.io.File;
import java.io.FilenameFilter;

public class ListDirectories {
    public String[] listDirectoryNames(String root) {
        return new File(root).list(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return new File(name).isDirectory();
            }
        });
    }
}

In Listing 1, I create a new anonymous inner class that overrides the accept() method, which specifies the filter criteria. In Groovy, I can skip the formality of creating a new class and simply coerce a closure into the interface, as shown in Listing 2:

Listing 2. Simulating the FilenameFilter interface in Groovy by using closure coercion
new File('.').list(
    { File dir, String name -> new File(name).isDirectory() }
     as FilenameFilter).each { println it }

In Listing 2, the list() method expects a FilenameFilter instance as an argument. Instead, I create a closure that matches the interface's accept() signature, and I implement the interface's functionality in the closure's body. After the closure definition, I coerce the closure into a proper FilenameFilter instance by using the call to as FilenameFilter. The Groovy as operator reifies the closure into a class that implements the interface. This technique works nicely for single-method interfaces, because a natural mapping exists between the method and the closure.

For interfaces that specify multiple methods, the reified class calls the same closure block for each method. But only in rare cases does it makes sense to handle all of the method invocations with the same code. When you need multiple methods, use a Map consisting of method name/closure pairs, instead of a single closure.

Maps

In Groovy, you can also use maps to represent interfaces. A map's keys are strings that represent method names, and the key's values are code blocks that implement method behavior. The example in Listing 3 reifies a map into an Iterator instance:

Listing 3. Using a map in Groovy to reify an interface
h = [hasNext: { h.i > 0 }, next: {h.i--}]
h.i = 10
def iterator = h as Iterator
                                                  
while (iterator.hasNext())
  print iterator.next() + ", "
// 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,

In Listing 3, I create a map (h) that contains hasNext and next keys and their respective code blocks. Groovy assumes that map keys are strings, so I don't need to surround the keys with quotation marks. Within each code block, I use dot notation (h.i) to reference a third key (i) of the h map. This dot notation, which is borrowed from familiar object syntax, is another example of syntactic sugar in Groovy. The code blocks won't execute until h acts as an iterator, and I must ensure that i has a value before I use h as an iterator. I set i's value with h.i = 10. Then I cast h as an Iterator and consume the collection of integers that starts with 10.

By enabling maps to act on the fly as instances of interfaces, Groovy greatly eases some of the syntactic friction that the Java language sometimes imposes. This feature illustrates nicely how the Java.next languages evolve the developer experience.


ExpandoMetaClass

As I illustrated in "Extension without inheritance, Part 1," you can use ExpandoMetaClass to add new methods to classes — including core classes such as Object and String. ExpandoMetaClass is also useful for several other purposes, such as adding methods to object instances and improving exception handling.

Adding methods to and removing methods from objects

Changes that you make to a class by using the ExpandoMetaClass take effect globally from the moment you attach the behavior to the class. This pervasiveness is the approach's advantage — not surprising, given that this expansion mechanism originated with the creation of the Grails web framework (see Resources). Grails relies on global changes to core classes. But sometimes you want to add semantics to a class in a limited way, without affecting all instances. For these cases, Groovy gives you a way to interact with an object's metaclass instance. For example, you can add methods to only a particular object instance, as in Listing 4:

Listing 4. Attaching behavior to an object instance
def list = new ArrayList()
list.metaClass.randomize = { ->
    Collections.shuffle(delegate)
    delegate
}

list << 1 << 2 << 3 << 4
println list.randomize() // [2, 1, 4, 3]
println list             // [2, 1, 4, 3]

In Listing 4, I create an instance (list) of ArrayList. Then I access the lazily instantiated metaClass property of the instance. I add a method (randomize()), which returns the shuffled collection. Within a metaclass method declaration, delegate represents the object instance.

However, my randomize() method mutates the underlying collection, because shuffle() is a mutating call. In the second line of output in Listing 4, notice that the collection is permanently changed to the new randomized order. Happily, you can easily change the default behavior of built-in methods such as Collections.shuffle() by working around their quirks. For example, the random property in Listing 5 is an improvement over Listing 4's randomize() method:

Listing 5. Improving undesirable semantics
def list2 = new ArrayList()
list2.metaClass.getRandom = { ->
  def l = new ArrayList(delegate)
  Collections.shuffle(l)
  l
}

list2 << 1 << 2 << 3 << 4
println list2.random // [4, 1, 3, 2]
println list2        // [1, 2, 3, 4]

In Listing 5, I make the body of the getRandom() method copy the list before mutating it, which leaves the original list unchanged. I also make random a property rather than a method by using the Groovy naming convention for automatically mapping properties to get and set methods.

Use of the properties technique to reduce the noise of extra parentheses led to recent changes in how methods chain together in Groovy. Version 1.8 of the language introduced the concept of command chains, which enable the creation of more-fluent domain-specific languages (DSLs). DSLs commonly augment existing classes or object instances to add special behavior.

Mixins

A popular feature in Ruby and similar languages is the mixin. Mixins give you the ability to add new methods and fields to existing hierarchies without using inheritance. Groovy supports mixins, as illustrated in Listing 6:

Listing 6. Using a mixin to attach behavior
class ListUtils {
  static randomize(List list) {
    def l = new ArrayList(delegate)
    Collections.shuffle(l)
    l
  }
}
List.metaClass.mixin ListUtils

In Listing 6, I create a helper class (ListUtils) and add a randomize() method to it. In the last line, I mix the ListUtils class into java.util.List, making my randomize() method available to java.util.List. You can use mixin with object instances, too. This technique facilitates debugging and tracing by confining changes to a separate code artifact, so it's the preferable way to attach behaviors to classes.

Combining extension points

Not only are Groovy's metaprogramming features powerful individually, but they also combine effectively. A common nicety in dynamic languages is a method missing hook — the ability for a class to respond to not-yet-defined methods in a controlled way rather than throw an exception. Groovy calls methodMissing() on a class that includes it, if an unknown method invocation occurs. You can include methodMissing() in additions that you make through ExpandoMetaClass. By combining methodMissing() with ExpandoMetaClass, you can add flexibility to existing classes such as Logger. Listing 7 shows an example:

Listing 7. Mixing ExpandoMetaClass and methodMissing
import java.util.logging.*

Logger.metaClass.methodMissing = { String name, args ->
    println "inside methodMissing with $name"
    int val = Level.WARNING.intValue() +
        (Level.SEVERE.intValue() - Level.WARNING.intValue()) * Math.random()
    def level = new CustomLevel(name.toUpperCase(),val)
    def impl = { Object... varArgs ->
        delegate.log(level,varArgs[0])
    }
    Logger.metaClass."$name" = impl
    impl args
}

Logger log = Logger.getLogger(this.class.name)
log.neal "really messed this up"
log.minor_mistake "can fix later"

In Listing 7, I use the ExpandoMetaClass to attach a methodMissing() method to the existing Logger class. Now I can be creative in the ways in which I invoke logs in later code wherever this Logger class is in scope, as shown in the last three lines of Listing 7.


Aspect-oriented programming

Aspect-oriented programming (AOP) is a popular and useful way to extend Java technology beyond its original design. By manipulating bytecode and the compilation process, aspects can "weave" new code into existing methods. AOP defines several terms, including pointcut, which is the location where the augmentation occurs. For example, a before pointcut refers to code that's added before a method invocation.

Because Groovy compilation produces Java bytecode, AOP is possible in Groovy too. But AOP can be replicated in Groovy through metaprogramming without the cumbersome formality that the Java language requires. The ExpandoMetaClass enables you to access a method so that you can save a reference to that method. Then you can redefine the method later and also still call the method's original version. Listing 8 illustrates this use of ExpandoMetaClass for AOP:

Listing 8. Using ExpandoMetaClass for aspect-oriented pointcuts
class Bank {
  def transfer(Account to, Account from, BigDecimal amount) {
    from.balance -= amount
    to.balance += amount
  }
}

class Account {
  def name, balance;

  @Override
  public String toString() {
    "Account{name:${name}, balance:${balance}}"
  }
}

def oldTransfer = 
  Bank.metaClass.getMetaMethod("transfer", [Account, Account, BigDecimal] as Object[])

Bank.metaClass.transfer = { Account to, Account from, BigDecimal amount ->
  println "Logging transfer: to: ${to}, from: ${from}, amount: ${amount}"
  oldTransfer.invoke(delegate, [to, from, amount] as Object[])
}

def bank = new Bank()
def acctA = new Account(name: "A", balance: 100.00)
def acctB = new Account(name: "B", balance: 200.00)
println("Balances: A = ${acctA.balance}, B = ${acctB.balance}")
bank.transfer(acctA, acctB, 10.00)
println("Balances: A = ${acctA.balance}, B = ${acctB.balance}")
//Balances: A = 100.00, B = 200.00
//Logging transfer: to: Account{name:A, balance:100.00},
//    from: Account{name:B, balance:200.00}, amount: 10.00
//Balances: A = 110.00, B = 190.00

In Listing 8, I create a typical Bank class with a lone transfer() method. The ancillary Account class holds simple account information. ExpandoMetaClass contains a getMetaMethod() method that retrieves a reference to a method. I use getMetaMethod() in Listing 8 to retrieve a reference to the existing transfer() method. Then, by using ExpandoMetaClass, I create a new transfer() method that displaces the old one. Within the body of the new method, after I write my logging statements, I invoke the original method.

Listing 8 contains an example of a before pointcut: I execute my "extra" code before I call the original method. This is a common technique in dynamic languages such as Ruby, whose community calls the technique Monkey Patching. (The original term was Guerilla Patching, but it was misheard as Gorilla Patching and then renamed to Monkey Patching as a play on words.) The result is the same as AOP, but the dynamic extensions in Groovy enable you to perform this enhancement within the language itself.


AST transformations

As powerful as the ExpandoMetaClass and its related features are, they can't reach all extension points. Ultimately, the most powerful metaprogramming is the ability to modify the compiler's Abstract Syntax Tree (AST) — the internal data structure that's maintained by the compilation process. Annotations are one of the hook locations where you can plug transformations in. Groovy predefines a number of useful language extensions as AST Transformations.

For example, the @Lazy annotation (@Lazy pets = ['Cat', 'Dog', 'Bird'], for example) defers instantiation of data structures until they are necessary for evaluation. Groovy 1.8 introduced a slew of useful structural annotations, a few of which appear in Listing 9:

Listing 9. Useful structural annotations in Groovy
import groovy.transform.*;

@Immutable
@TupleConstructor
@EqualsAndHashCode
@ToString(includeNames = true, includeFields=true)
final class Point {
  int x
  int y
}

In Listing 9, the Groovy runtime automatically:

  • Generates tuple-style constructors
  • Generates both equals() and hashCode() methods
  • Makes the Point class immutable
  • Generates a toString() method

Use of AST transformations is far superior to use of an IDE or reflection to generate infrastructure methods. With an IDE, you must always remember to regenerate the methods when changes occur. And reflection is slower than code generation that occurs at compilation time.

In addition to consuming the rich variety of predefined AST Transformations, you can use a full API that Groovy provides for building your own. With this API, you can access the most granular of underlying abstractions to change how code is generated.


Conclusion

In this installment, you learned about the dizzying array of extension options that Groovy offers through its metaprogramming features. In the next Java.next installment, I explore traits (mixin functionality) and other metaprogramming in Scala.

Resources

Learn

  • The Productive Programmer (Neal Ford, O'Reilly Media, 2008): Neal Ford's book expands on a number of the topics in this series.
  • Clojure: Clojure is a modern, functional Lisp that runs on the JVM.
  • Scala: Scala is a modern, functional language on the JVM.
  • Groovy: Groovy is a dynamic variant of the Java language, with updated syntax and capabilities.
  • Grails: Grails is a popular web framework that's written in Groovy.
  • Practically Groovy (Andrew Glover and Scott Davis, developerWorks, 2004-2009): Explore the practical uses of Groovy in this developerWorks series and learn when and how to apply them successfully.
  • Mastering Grails (Scott Davis, developerWorks, 2008-2009): Learn all about Grails in this article series.
  • Advanced Groovy Tips and Tricks: Thanks to Ken Kousen for some of the advanced Groovy examples in this article, which were taken from this excellent presentation.
  • Functional thinking: Explore functional programming in Neal Ford's column series on developerWorks.
  • More articles by this author (Neal Ford, developerWorks, June 2005-current): Learn about Groovy, Scala, Clojure, functional programming, architecture, design, Ruby, Eclipse, and other Java-related technologies.
  • developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.

Get products and technologies

  • Download IBM product evaluation versions and get your hands on application development tools and middleware products from DB2®, Lotus®, Rational®, Tivoli®, and WebSphere®.

Discuss

  • Get involved in the developerWorks community. Connect with other developerWorks users as you explore the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java technology, Open source
ArticleID=942795
ArticleTitle=Java.next: Extension without inheritance, Part 3
publish-date=09032013