1. 概要

この入門チュートリアルでは、この動的で強力なJVM言語の重要な機能であるGroovyのクロージャの概念について説明します。

JavascriptやPythonを含む他の多くの言語は、クロージャの概念をサポートしています。 ただし、クロージャーの特性と機能は言語によって異なります。

Groovyクロージャーの重要な側面に触れ、その過程でどのように使用されるかの例を示します。

2. 閉鎖とは何ですか?

closureは匿名のコードブロックです。 Groovyでは、Closureクラスインスタンスです。 クロージャは0個以上のパラメータを取り、常に値を返すことができます。

さらに、クロージャは、スコープ外の周囲の変数にアクセスし、実行中にローカル変数とともにそれらを使用する場合があります。

さらに、クロージャを変数に割り当てたり、パラメータとしてメソッドに渡したりすることができます。 したがって、クロージャは実行を遅らせるための機能を提供します。

3. 閉鎖宣言

Groovy Closureには、パラメーター、矢印->、および実行するコードが含まれています。パラメーターはオプションであり、指定されている場合はコンマで区切られます。

3.1. 基本宣言

def printWelcome = {
    println "Welcome to Closures!"
}

ここで、クロージャー printWelcome は、呼び出されたときにステートメントを出力します。 それでは、単項クロージャの簡単な例を書いてみましょう。

def print = { name ->
    println name 
}

ここで、クロージャー print は1つのパラメーター( name )を取り、呼び出されたときにそれを出力します。

クロージャの定義はメソッドに似ているので、それらを比較してみましょう。

def formatToLowerCase(name) {
    return name.toLowerCase()
}
def formatToLowerCaseClosure = { name ->
    return name.toLowerCase()
}

ここでは、メソッドと対応するクロージャは同じように動作します。 ただし、クロージャとメソッドには微妙な違いがあります。これについては、クロージャとメソッドのセクションで後ほど説明します。

3.2. 実行

クロージャは2つの方法で実行できます。他のメソッドと同じように呼び出すか、callメソッドを使用できます。

たとえば、通常の方法として:

print("Hello! Closure")
formatToLowerCaseClosure("Hello! Closure")

そして、callメソッドで実行します。

print.call("Hello! Closure") 
formatToLowerCaseClosure.call("Hello! Closure")

4. パラメーター

Groovyクロージャのパラメータは、通常のメソッドのパラメータと似ています。

4.1. 暗黙のパラメータ

パラメーターが定義されていない場合、Groovyは「it」という名前の暗黙的なパラメーターを想定するため、パラメーターなしで単一クロージャーを定義できます。

def greet = {
    return "Hello! ${it}"
}
assert greet("Alex") == "Hello! Alex"

4.2. 複数のパラメータ

これは、2つのパラメーターを受け取り、それらを乗算した結果を返すクロージャーです。

def multiply = { x, y -> 
    return x*y 
}
assert multiply(2, 4) == 8

4.3. パラメータタイプ

これまでの例では、パラメーターで指定されたタイプはありません。 クロージャパラメータのタイプを設定することもできます。 たとえば、 multiply メソッドを書き直して、他の操作を検討してみましょう。

def calculate = {int x, int y, String operation ->
    def result = 0    
    switch(operation) {
        case "ADD":
            result = x+y
            break
        case "SUB":
            result = x-y
            break
        case "MUL":
            result = x*y
            break
        case "DIV":
            result = x/y
            break
    }
    return result
}
assert calculate(12, 4, "ADD") == 16
assert calculate(43, 8, "DIV") == 5.375

4.4. Varargs

通常のメソッドと同様に、クロージャで可変数の引数を宣言できます。 例えば:

def addAll = { int... args ->
    return args.sum()
}
assert addAll(12, 10, 14) == 36

5. 議論としての閉鎖

Closureを引数として通常のGroovyメソッドに渡すことができます。 これにより、メソッドはクロージャを呼び出してタスクを完了し、動作をカスタマイズできます。

簡単なユースケースについて説明しましょう。通常の数字の体積の計算です。

この例では、体積は面積に高さを掛けたものとして定義されています。 ただし、面積の計算は形状によって異なる場合があります。

したがって、 volume メソッドを記述します。このメソッドは、引数としてクロージャ areaCalculator を取り、呼び出し中に面積計算の実装を渡します。

def volume(Closure areaCalculator, int... dimensions) {
    if(dimensions.size() == 3) {
        return areaCalculator(dimensions[0], dimensions[1]) * dimensions[2]
    } else if(dimensions.size() == 2) {
        return areaCalculator(dimensions[0]) * dimensions[1]
    } else if(dimensions.size() == 1) {
        return areaCalculator(dimensions[0]) * dimensions[0]
    }    
}
assert volume({ l, b -> return l*b }, 12, 6, 10) == 720

同じ方法を使用して円錐の体積を見つけましょう。

assert volume({ radius -> return Math.PI*radius*radius/3 }, 5, 10) == Math.PI * 250

6. ネストされたクロージャ

クロージャー内でクロージャーを宣言して呼び出すことができます。

たとえば、すでに説明したcalculateクロージャにロギング機能を追加しましょう。

def calculate = {int x, int y, String operation ->
        
    def log = {
        println "Performing $it"
    }
        
    def result = 0    
    switch(operation) {
        case "ADD":
            log("Addition")
            result = x+y
            break
        case "SUB":
            log("Subtraction")
            result = x-y
            break
        case "MUL":
            log("Multiplication")
            result = x*y
            break
        case "DIV":
            log("Division")
            result = x/y
            break
    }
    return result
}

7. 文字列の遅延評価

Groovy String は通常、作成時に評価および補間されます。 例えば:

def name = "Samwell"
def welcomeMsg = "Welcome! $name"
        
assert welcomeMsg == "Welcome! Samwell"

name 変数の値を変更しても、welcomeMsgは変更されません。

name = "Tarly"
assert welcomeMsg != "Welcome! Tarly"

クロージャ補間により、文字列の遅延評価を提供し、文字列の周囲の現在の値から再計算できます。 例えば:

def fullName = "Tarly Samson"
def greetStr = "Hello! ${-> fullName}"
        
assert greetStr == "Hello! Tarly Samson"

今回のみ、変数を変更すると、補間された文字列の値にも影響します。

fullName = "Jon Smith"
assert greetStr == "Hello! Jon Smith"

8. コレクションのクロージャ

Groovyコレクションは、多くのAPIでクロージャを使用しています。 たとえば、アイテムのリストを定義し、単項クロージャ each を使用してそれらを出力してみましょう。これには、暗黙のパラメータがあります。

def list = [10, 11, 12, 13, 14, true, false, "BUNTHER"]

list.each {
    println it
}

assert [13, 14] == list.findAll{ it instanceof Integer && it >= 13 }

多くの場合、いくつかの基準に基づいて、マップからリストを作成する必要があります。 例えば:

def map = [1:10, 2:30, 4:5]

assert [10, 60, 20] == map.collect{it.key * it.value}

9. 閉鎖と方法

これまで、クロージャの構文、実行、およびパラメータについて見てきましたが、これらはメソッドとかなり似ています。 クロージャとメソッドを比較してみましょう。

通常のGroovyメソッドとは異なり:

  • Closureを引数としてメソッドに渡すことができます
  • 単項クロージャは、暗黙のitパラメータを使用できます
  • Closure を変数に割り当て、後でメソッドとして、またはcallを使用して実行できます。
  • Groovyは、実行時にクロージャの戻りタイプを決定します
  • クロージャー内でクロージャーを宣言して呼び出すことができます
  • クロージャーは常に値を返します

したがって、クロージャには通常の方法よりも利点があり、Groovyの強力な機能です。

10. 結論

この記事では、Groovyでクロージャーを作成する方法を確認し、それらがどのように使用されるかを調べました。

クロージャは、実行を遅らせるためにオブジェクトとメソッドに機能を注入する効果的な方法を提供します。

いつものように、この記事のコードと単体テストは、GitHubから入手できます。