程式扎記: [ In Action ] Working with closures - Declaring closures

標籤

2014年1月28日 星期二

[ In Action ] Working with closures - Declaring closures

Preface:
So far, we have used the simple abbreviated syntax of closures: After a method call, put your code in curly braces with parameters delimited from the closure body by an arrow. Let’s start by adding to your knowledge about the simple abbreviated syntax, and then we’ll look at two more ways to declare a closure: by using them in assignments and by referring to a method.

The simple declaration:
Listing 5.1 shows the simple closure syntax plus a new convenience feature. When there is only one parameter passed into the closure, its declaration is optional. The magic variable it can be used instead. See the two equivalent closure declarations in listing 5.1.
Listing 5.1 Simple abbreviated closure declaration
  1. log = ''  
  2. (1..10).each{ counter -> log += counter }  
  3. assert log == '12345678910'  
  4. log = ''  
  5. (1..10).each{ log += it }  
  6. assert log == '12345678910'  
Note that unlike counter , the magic variable it needs no declaration.

This syntax is an abbreviation because the closure object as declared by the curly braces is the last parameter of the method and would normally appear within the method’s parentheses. As you will see, it is equally valid to put it inside parentheses like any other parameter, although it is hardly ever used this way:
  1. log = ''  
  2. (1..10).each({ log += it })  
  3. assert log == '12345678910'  
This syntax is simple because it uses only one parameter, the implicit parameter it . Multiple parameters can be declared in sequence, delimited by commas. A default value can optionally be assigned to parameters, in case no value is passed from the method to the closure. We will show examples in section 5.4.

Using assignments for declaration:
A second way of declaring a closure is to directly assign it to a variable:
  1. def printer = { line -> println line }   
The closure is declared inside the curly braces and assigned to the printer variable.
TIP. Whenever you see the curly braces of a closure, think: new Closure(){} .

There is also a special kind of assignment, to the return value of a method:
  1. def Closure getPrinter() {  
  2.     return { line -> println line }  
  3. }  
Again, the curly braces denote the construction of a new closure object. This object is returned from the method call.
TIP.
Curly braces can denote the construction of a new closure object or a Groovy block. Blocks can be class, interface, static or object initializers, or method bodies; or can appear with the Groovy keywords if , else , synchronized , for , while , switch , try , catch , and finally . All other occurrences are closures.

As you see, closures are objects. They can be stored in variables, they can be passed around, and, as you probably guessed, you can call methods on them. Being objects, closures can also be returned from a method.

Referring to methods as closures:
The third way of declaring a closure is to reuse something that is already declared: a method. Methods have a body, optionally return values, can take parameters, and can be called. The similarities with closures are obvious, so Groovy lets you reuse the code you already have in methods, but as a closure. Referencing a method as a closure is performed using the reference .& operator. The reference is used to specify which instance should be used when the closure is called, just like a normal method call to reference.someMethod() . Figure 5.1 shows an assignment using a method closure, breaking the statement up into its constituent parts.


Below demonstrates method closures in action, showing two different instances being used to give two different closures.
- Listing 5.2 Simple method closures in action
  1. class MethodClosureSample {  
  2.     int limit  
  3.       
  4.     MethodClosureSample (int limit) {     
  5.         this.limit = limit                
  6.     }                                       
  7.       
  8.     boolean validate (String value) {       
  9.         return value.length() <= limit      
  10.     }                                       
  11. }  
  12.   
  13. // 1) Normal constructor calls  
  14. MethodClosureSample first = new MethodClosureSample (6)      
  15. MethodClosureSample second = new MethodClosureSample (5)     
  16.   
  17. // 2) Method closure assignment  
  18. Closure firstClosure = first.&validate  
  19.   
  20. def words = ['long string''medium''short''tiny']  
  21. assert 'medium' == words.find (firstClosure)        // 3) Calling the closure  
  22. assert 'short' == words.find (second.&validate)     // 4) Passing a method closure directly  
Method closures are limited to instance methods, but they do have another interesting feature—runtime overload resolution, also known as multimethods. You will find out more about multimethods in chapter 7, but listing 5.3 gives a taste.
- Listing 5.3 Multimethod closures—the same method name called with different parameters is used to call different implementations
  1. class MultiMethodSample {  
  2.       
  3.     int mysteryMethod (String value) {  
  4.         return value.length()  
  5.     }  
  6.       
  7.     int mysteryMethod (List list) {  
  8.         return list.size()  
  9.     }  
  10.       
  11.     int mysteryMethod (int x, int y) {  
  12.         return x+y  
  13.     }  
  14. }  
  15. MultiMethodSample instance = new MultiMethodSample()  
  16. // 1) Only a single closure is created  
  17. Closure multi = instance.&mysteryMethod         
  18.   
  19. // 2) Different implementations are called based on argument types          
  20. assert 10 == multi ('string arg')                
  21. assert 3 == multi (['list''of''values'])     
  22. assert 14 == multi (68)   
Comparing the available options:
Listing 5.4 shows all of these ways of creating and using closures: through simple declaration, assignment to variables, and method closures. In each case, we call theeach method on a simple map, providing a closure that doubles a single value. By the time we’ve finished, we’ve doubled each value three times.
- Listing 5.4 Full closure declaration examples
  1. map = ['a':1'b':2]                                
  2. map.each{ key, value -> map[key] = value * 2 }  // 1) Parameter sequence with commas   
  3. assert map == ['a':2'b':4]                        
  4.                                              
  5. // 2) Assign and then call a closure reference         
  6. doubler = {key, value -> map[key] = value * 2 }     
  7. map.each(doubler)                                   
  8. assert map == ['a':4'b':8]                        
  9.            
  10. // 3) A usual method declaration                                           
  11. def doubleMethod (entry){                
  12.     map[entry.key] = entry.value * 2     
  13. }           
  14.   
  15. // 4) Reference and call a method as a closure                               
  16. doubler = this.&doubleMethod      
  17. map.each(doubler)                 
  18. assert map == ['a':8'b':16]  
In order to fully understand how closures work and how to use them within your code, you need to find out how to invoke them. That is the topic of the next section.
- Listing 7.5 Declaring methods
  1. class TestClass {  
  2.     static void main(args) {  
  3.         def some = new TestClass()  
  4.         some.publicVoidMethod()  
  5.         assert 'hi' == some.publicUntypedMethod()  
  6.         assert 'ho' == some.publicTypedMethod()  
  7.         combinedMethod() // Call static method of current class  
  8.     }  
  9.     void publicVoidMethod(){}  
  10.     def publicUntypedMethod(){return 'hi'}  
  11.     String publicTypedMethod(){return 'ho'}  
  12.     protected static final void combinedMethod(){}  
  13. }  
The main method has some interesting twists. First, the public modifier can be omitted because it is the default. Second, args usually has to be of type String[] in order to make the main method the one to start the class execution. Thanks to Groovy’s method dispatch, it works anyway, although args is now implicitly of static typejava.lang.Object . Third, because return types are not used for the dispatch, we can further omit the void declaration.
NOTE.
The Java compiler fails on missing return statements when a return type is declared for the method. In Groovy, return statements are optional, and therefore it’s impossible for the compiler to detect “accidentally” missing returns.

This message was edited 25 times. Last update was at 17/01/2014 11:47:05

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!