Scott Davis will continue the discussion of Groovy metaprogramming, and this time he will delve into @Delegate annotations, @Delegate annotations blur the distinction between data types and behavior and static and dynamic types.
In the past few sessions of groovy, you've learned how groovy language features such as closures and metaprogramming add dynamic functionality to Java™ development. This article provides more content in this area. You will see how @Delegate annotations evolve from the Delegate used by Expandometaclass. Once again, you'll see how Groovy's dynamic features make it the ideal language for unit testing.
In the article "Using closures, Expandometaclass, and categories for metaprogramming", you learned about the concept of delegate. When you add a shout () method to Java.lang.String's Expandometaclass, you use delegate to represent the relationship between the two classes, as shown in Listing 1:
Listing 1. Use delegate to access String.touppercase ()
String.metaClass.shout = {->
return delegate.toUpperCase()
}
println "Hello MetaProgramming".shout()
//output
HELLO METAPROGRAMMING
You cannot represent this.touppercase () because the Expandometaclass does not contain the toUpperCase () method. Similarly, it cannot be represented as super.touppercase () because the Expandometaclass does not extend a String. (In fact, it is not possible to extend a string because string is a final class). The Java language does not have a vocabulary for representing the symbiotic relationship between these two classes. That's why Groovy has to introduce the delegate concept.
In Groovy 1.6, @Delegate annotations are added to the language. This annotation allows you to add one or more delegates to any class-not just expandometaclass.
To fully understand the power of @Delegate annotations, consider a common but complex task in Java programming: Create a new class on the basis of the final class.
Composite mode and final class
Suppose you want to create a allcapsstring class that has all the behavior of java.lang.String, the only difference being-as the name implies-the value always returns in uppercase. A String is the product of a final class-java evolution to its end. Listing 2 shows that you cannot extend a String directly:
Listing 2. It is impossible to extend the final class
class AllCapsString extends String{
}
$ groovyc AllCapsString.groovy
org.codehaus.groovy.control.MultipleCompilationErrorsException:
startup failed, AllCapsString.groovy: 1: You are not allowed to
overwrite the final class 'java.lang.String'.
@ line 1, column 1.
class AllCapsString extends String{
^
1 error
This code is not valid, so your next best option is to use compliance mode, as shown in Listing 3:
Listing 3. Use compound mode for new type of String class
class AllCapsString{
final String body
AllCapsString(String body){
this.body = body.toUpperCase()
}
String toString(){
body
}
//now implement all 72 String methods
char charAt(int index){
return body.charAt(index)
}
//snip...
//one method down, 71 more to go...
}