Skip to end of metadata
Go to start of metadata

쉬운 가이드

자바 언어에서는 대부분의 실행 가능한 코드가 정적 클래스 메서드 혹은 인스턴스 메서드 안에 들어 있습니다(물론 생성자, 생성 표현식(initilization expressions) 등에도 들어 있지만, 이 논의에서 중요한 사항은 아닙니다). 메서드는 { 괄호로 둘러쌓여 있는 코드 블럭, 그리고 그 블럭에 대한 메서드 이름으로 구성됩니다. 이러한 모든 메서드들은 어떤 타입의 클래스 안에 정의되어야만 합니다. 예를 들어 주어진 정수의 제곱을 반환하는 메서드는 다음과 같이 작성합니다:

그리고 square() 메서드를 호출하려면 클래스에 대한 참조와 메서드 이름을 다음과 같이 써주어야 합니다:

You can do the same thing in Groovy, but in groovy you can define the code without having to declare a class and a method. This is done using a closure. A closure is one or more program statements enclosed in curly brackets. The main difference between a closure and method is that closures do not require a class or a method name. The following shows the square() method re-written as a closure.

As you can see, the executable code is the same except you didn't need to declare a class or assign the code a method name. While illustrative, the previous example is not all that useful because there is no way to use that closure once its created. It has no identifier (method name) so how can you call it? To fix that you assign the closure to a variable when it's created. You can than treat that variable as the identifier of the closure and make calls on it as follows:

What is really nice about closures is that you can create a closure, assign it to a variable, and then pass it around your program like any other variable. At first this seems a bit, well useless, but as you learn more about Groovy you'll discover that closures are used all over the place.

As example, let extend the java.util.Vector class in Groovy by adding a single method that allows you to apply a closure to every element in the vector. My new class, GVector, looks as follows:

The apply() method takes a closure as an input parameter. For each element in the GVector, the closure is called passing in the element. The resulting value is then used to replace the element. The idea is that you can modify the contents of the GVecotor in place using a closure which takes each element and converts into something else.

Now we can call our new apply() method with any closure we want. For example, we will create a new GVector, populate it with some elements, and the pass in closure we created earlier, the one that squares an integer value.

Because the apply() method on the GVector can be used with any closure, you can use any closure. For example, the following uses a closure that simply prints out the item its passed.

If you were to run the above script, assuming you defined and compiled the GVector class, the output would look like this:

In addition to assigning closures to variables, you can also declare them directly as arguments to methods. For example, the above code could be re-written in the following manner:

This example accomplishes the same thing as the first, but the closure is defined directly as an argument to the apply method of GVector.

클로저와 코드 블록

A closure looks a lot like a regular Java or Groovy code block, but actually it's not the same. The code within a regular code block (whether its a method block, static block, synchronized block, or just a block of code) is executed by the virtual machine as soon as it's encountered. With closures the statements within the curly brackets are not executed until the call() is made on the closure. In the previous example the closure is declared in line, but it's not executed at that time. It will only execute if the call() is explicitly made on the closure. This is an important differentiator between closures and code blocks. They may look the same, but they are not. Regular Java and Groovy blocks are executed the moment they are encountered; closures are only executed if the call() is invoked on the closure.

정확한 가이드

A closure in Groovy is an anonymous chunk of code that may take arguments, return a value, and reference and use variables declared in its surrounding scope. In many ways it resembles anonymous inner classes in Java, and closures are often used in Groovy in the same way that Java developers use anonymous inner classes. However, Groovy closures are much more powerful than anonymous inner classes, and far more convenient to specify and use.

클로저 정의 문법

A closure definition follows this syntax:

Where [closureArguments->] is an optional comma-delimited list of arguments, and statements are 0 or more Groovy statements. The arguments look similar to a method's parameter list, and these arguments may be typed or untyped. When a parameter list is specified, the -> character is required and serves to seperate the arguments from the closure body. The statements portion consists of 0, 1, or many Groovy statements.

Some examples of valid closure definitions:

++++ Note: The examples could definitely be made more real-life MWS

클로저의 의미론(semantics)

Closures appear to be a convenient mechanism for defining something like an inner classs, but the semantics are in fact more powerful and subtle than what an inner class offers. In particular, the properties of closures can be summarized in this manner:

  1. They have one implicit method (which is never specified in a closure definition) called doCall()
  2. A closure may be invoked via the call() method, or with a special syntax of an unnamed () invocation. Either invocation will be translated by Groovy into a call to the Closure's doCall() method.
  3. Closures may have 1...N arguments, which may be statically typed or untyped. The first parameter is available via an implicit untyped argument named it if no explicit arguments are named. If the caller does not specify any arguments, the first parameter (and, by extension, it) will be null.
  4. The developer does not have to use it for the first parameter. If they wish to use a different name, they may specify it in the parameter list.
  5. Closures always return a value. This may occur via either an explicit return statement, or as the value of the last statement in the closure body (e.g. an explicit return statement is optional).
  6. A closure may reference any variables defined within its enclosing lexical scope. Any such variable is said to be bound to the closure
  7. Any variables bound to a closure are available to the closure even when the closure is returned outside of the enclosing scope.
  8. Closures are first class objects in Groovy, and are always derived from the class Closure. Code which uses closures may reference them via untyped variables or variables typed as Closure.
  9. The body of a closure is not executed until it is explicitly invoked e.g. a closure is not invoked at its definition time
  10. A closure may be curried so that one a copy the closure is made with one or more of its parameters fixed to a constant value

These properties are explained further in the following sections.

클로저는 익명

Closures in Groovy are always represented as anonymous blocks. Unlike a Java or Groovy class, you cannot have a named closure. You may however reference closures using untyped variables or variables of type Closure, and pass such references as method arguments and arguments to other closures.

암묵적 메서드

Closures are considered to have one implicitly defined method, which corresponds to the closure's arguments and body. You cannot override or redefine this method. This method is always invoked by the call() method on the closure, or via the special unnamed () syntax. The implicit method name is doCall().

클로저 인자

A closure always has at least one argument, which will be available within the body of the closure via the implicit parameter it if no explicit parameters are defined. The developer never has to declare the it variable - like the this parameter within objects, it is implicitly available.

If a closure is invoked with zero arguments, then it will be null.

Explicit closure arguments may be specified by the developer as defined in the syntax section. These arguments are a list of 1 or more argument names which are comma seperated. The parameter list is terminated with a -> character. Each of these arguments may be specified "naked" e.g. without a type, or with an explicit static type. If an explicit parameter list is specified, then the it variable is not available.

For arguments that have a declared type, this type will be checked at runtime. If a closure invocation has 1 or more arguments which do not match the declared argument type(s), then an exception will be thrown at runtime. Note that this argument type checking always occurs at runtime; there is no static type checking involved, so the compiler will not warn you about mis-matched types.

Groovy has special support for excess arguments. A closure may be declared with its last argument of type Object[]. If the developer does this, any excess arguments at invocation time are placed in this array. This can be used as a form of support for variable numbers of arguments. For example:

Both invocations of c are valid. Since the closure defines two arguments (format and args) and the last argument is of type Object[], the first parameter in any call to c will be bound to the format argument and the remaining parameters will be bound to the args argument. In the first call of c the closure will receive the parameter args with 2 elements ("two", "three") while the format parameter will contain the string "one". In the second call the closure will receive the parameter args with no elements and the format parameter will contain the string "1".

++++ What Exception is thrown? MWS

클로저 반환 값

Closures always have a return value. The value may be specified via one or more explicit return statement in the closure body, or as the value of the last executed statement if returnis not explicitly specified. If the last executed statement has no value (for example, if the last statement is a call to a void method), then null is returned.

There is currently no mechanism for statically declaring the return type of a closure.

외부 변수 참조

Closures may reference variables external to their own definition. This includes local variables, method parameters, and object instance members. However, a closure may only reference those variables that the compiler can lexically deduce from the physical location of the closure definition within the source file.

Some examples might serve to clarify this. The following example is valid and shows a closure using a method's local variables and a method parameter:

The above code will print out:

Looking at the definition of class A, the closure inside of publicMethodhas access to all variables that publicMethod may legally access. This is true whether the variables are local variables, parameters, instance members, or method invocations.

When a closure references variables in this way, they are bound to the closure. At the same time, the variables are still available normally to the enclosing scope, so the closure may read/change any such values, and code from the outer scope may read/change the same variables.

If such a closure is returned from its enclosing scope, the variables bound with the closure also live on. This binding occurs when the closure is instantiated. If an object method or instance member is used within a closure, then a reference to that object is stored within the closure. If a local variable or parameter is referenced, then the compiler re-writes the local variable or parameter reference so that the local variable or parameter is taken off the stack and stored in an heap based object.

It's important to keep in mind that these references only are ever allowed according to the lexical structure available to the compiler (in this case, the A class). This process does not occur dynamically by looking at the call stack. So the following will not work:

The above code is similar to the first example, except that we now have a class B which dynamically instantiates an object of type Aand then calls A.publicMethod(). However, in this code the closure within publicMethod() is trying to reference a member from B, and this is not allowed since the compiler cannot statically determine that this is available. Some older languages allowed this sort of reference to work, by dynamically examining the call stack at runtime, but this is disallowed in Groovy.

Groovy supports the special owner variable which can be used when a closure argument is hiding an object member variable. For example:

In the above code the println (name) call is referencing the parameter name. If the closure needs to access the name instance variable of class HiddenMember, it can use the owner variable to indicate this:

클로저 타입

All closures defined in Groovy are derived from the type Closure. Each unique closure definition with a Groovy program creates a new unique class which extends Closure. If you wish the specify the type of a closure in a parameter, local variable, or object member instance, then you should use the Closure type.

The exact type of a closure is not defined unless you are explicitly subclasses the Closure class. Using this example:

The exact type of the closure referenced by c is not defined, we know only that it is a subclass of Closure.

Closure creation and invocation

Closures are created implicitly when their surrounding scope encounters them. For example, in the following code two closures are created:

In the above example, closureVar holds a reference to a different closure object than closureVar2. Closures are always implicitly created in this manner - you cannot new a closure programmatically.

Closures may be invoked using one of two mechanisms. The explict mechanism is to use the call() method:

You may also use the implict nameless invocation approach:

If you are looking at the Closure javadoc, you may notice that the call method within the Closure class is defined as:

Despite this method signature, you do not have to manually write code to turn parameters into the Object[] array. Instead, invocations use the normal method argument syntax, and Groovy converts such calls to use an object array:

Both calls above are legal Groovy. However, if you are dealing with a Closure from Java code you will need to create the Object[] array yourself (smile)

curry()를 이용하여 클로저 인자를 상수로 고정하기

You can fix the values for one or more arguments to a closure instance using the curry() method from the Closuretype. In fact, this action is often referred to as currying in functional programming circles, and the result is generally referred to as a Curried Closure. Curried closures are very useful for creating generic closure definitions, and then creating several curried versions of the original with differing parameters bound to them.

When the curry() method is called on a closure instance with one or more arguments, a copy of the closure is first made. The incoming arguments are then bound permanently to the new closure instance so that the parameters 1..N to the curry() call are bound to the 1..N parameters of the closure. The new curried closure is then returned the caller.

Callers to the new instance will have their invocation parameters bound to the new closure in the N+1 parameter position of the original closure.

A simple example of this would be:

The above code defines a closure c, and then calls c.curry("foo"). This returns a curried closure with the arg1 value permanently bound to the value "foo". On the invocation d("bar"), the "bar" parameter comes into the closure in the arg2 argument. The resulting output would be foo bar.

특별한 경우: 클로저를 메서드로 전달하기

Groovy has a special case for defining closures as method arguments to make the closure syntax easier to read. Specifically, if the last argument of a method is of type Closure, you may invoke the method with an explicit closure block outside of the parenthesis. For example, if a class has a method:

Then you may invoke each() with a closure definition outside of the parenthesis:

The more traditional syntax is also available, and also note that in Groovy you can elide parenthesis in many situations, so these two variations are also legal:

The same rule applies even if the method has other arguments. The only restriction is that the Closure argument must be last:

This syntax is only allowed when explicitly defining a closure within the method call. You cannot do this with a variable of type closure, as this example shows:

When you are not defining a closure inline to a method call, you cannot use this syntax and must use the more verbose syntax:

클로저와 익명 내부 클래스의 비교

Groovy includes closures because they allow the developer to write more concise and more easily understood code. Where Java developers may use single-method interfaces (Runnable, the Command pattern) combined with anonymous inner classes, Groovy allows you to accomplish the same sort of tasks in a less verbose manner. In addition, closures have fewer constraints than anonymous inner classes and include extra functionality.

Most closures are relatively short, isolated, and anonymous snippets of code that accomplish one specific job. Their syntax is streamlined to make closure definitions very short and easy to read without additional clutter. For example, in Java code you might see code like this for an imaginary GUI system:

The same code in Groovy would look like this:

The Groovy code accomplishes the same task but is much clearer and without extra syntactical clutter. This is the first
rule of Groovy closures - closures are trivially easy to write. In addition, closures may reference any variables in its outer
defining scope without the restrictions of anonymous inner classes - in particular, such variables do not need to be final.
Closures also carry their state around with them, even when they reference local variables and parameters. Closures may also take advantage of Groovy's optional dynamic typing so that you don't have to statically declare all of your closure arguments or return types (in fact, a Groovy closure can take varying numbers of parameters from invocation to invocation).

What Groovy closures lack compared to an approach using Command-like interfaces is the level of static typing involved. A Java interface rigidly enforces what type of objects can be used and the method(s) that may be called in it. In Groovy, all closures type equally as Closure and type checking of arguments (if specified in the closure definition) is deferred until Runtime.


You can see more documentation on closures

Error formatting macro: link: java.lang.IllegalArgumentException: Link needs a name and a URL as arguments.

디랙티브를 이용한 Groovy 확장

You can provide your own specialized methods supporting closures by implementing a Java class containing such methods. These methods must be static and contain at least two parameters. The first parameter to the method must be the type on which the method should operate, and the last parameter must be a Closure type.

Consider the example below, which is a variant of the eachFile method which simply ignores files, and just prints the directories within the dir object on which the method operates.

Take note of the use() directive. This will tell groovy where the eachDir method is implemented. Below is the Java code required to support the eachDir method on the File object, as shown.

To support additional parameters, these should be placed between the first and last.

  • No labels