Замыкание (closure) представляет объект функции, который запоминает свое лексическое окружение даже в том случае, когда она выполняется вне своей области видимости.
Технически замыкание включает три компонента:
внешняя функция, которая определяет некоторую область видимости и в которой определены некоторые переменные и параметры - лексическое окружение
переменные и параметры (лексическое окружение), которые определены во внешней функции
вложенная функция, которая использует переменные и параметры внешней функции
fun outer(): ()->Unit{ // внешняя функция var n // некоторая переменная - лексическое окружение fun inner(){ // вложенная функция // действия с переменной n } return ::inner // возвращаем вложенную функцию }
В данном случае функция inner и есть замыкание.
Рассмотрим замыкания на простейшем примере:
fun main() { val fn = outer() // fn = inner, так как функция outer возвращает функцию inner // вызываем внутреннюю функцию inner fn() // 6 fn() // 7 fn() // 8 } fun outer(): ()->Unit{ // внешняя функция var n = 5 // некоторая переменная - лексическое окружение функции inner fun inner(){ // вложенная функция // действия с переменной n n++ println(n) } return ::inner }
Здесь функция outer задает область видимости, в которой определены внутренняя функция inner и переменная n. Переменная n представляет лексическое окружение для функции inner. В самой функции inner инкрементируем переменную n и выводим ее значение на консоль. В конце функция outer возвращает функцию inner.
Далее вызываем функцию outer:
val fn = outer()
Поскольку функция outer возвращает функцию inner, то переменная fn будет хранить ссылку на функцию inner. При этом эта функция запомнила свое окружение - то есть внешнюю переменную n.
Далее мы фактически три раза вызываем функцию inner, и мы видим, что переменная n, которая определена вне функции inner, увеличивается на единицу:
fn() // 6 fn() // 7 fn() // 8
То есть несмотря на то, что переменная x определена вне функции inner, эта функция запомнила свое окружение и может его использовать, несомотря на то, что она вызывается вне функции outer, в которой была определена. В этом и суть замыканий.
Кстати мы можем сократить определение функции outer, используя анонимную функцию:
fun outer(): ()-> Unit{ var n = 5 return { n++ println(n) } }
Рассмотрим другой пример:
fun main() { val func = multiply(5) val result1 = func(6) // 30 println(result1) // 30 val result2 = func(5) // 25 println(result2) // 25 } fun multiply(n:Int): (Int)->Int{ return {m:Int-> n * m} }
Итак, здесь вызов функции multiply()
приводит к вызову другой внутренней функции. Внутренняя же функция представлена лямбда-выражением:
{m:Int-> n * m}
Оно запоминает окружение, в котором было создана, в частности, значение параметра n.
В итоге при вызове функции multiply определяется переменная func
, которая и представляет собой замыкание, то есть объединяет две вещи:
функцию и окружение, в котором функция была создана. Окружение состоит из любой локальной переменной или любого параметра, которые были определены в области действия функции multiply во время создания замыкания.
То есть result1 — это замыкание, которое содержит и внутреннюю функцию {m:Int-> n * m}
, и параметр n, который существовал во время создания замыкания.
При этом важно не запутаться в параметрах. При определении замыкания:
val func = multiply(5)
Число 5 передается для параметра n функции multiply.
При вызове внутренней функции:
val result1 = func(6) // 30
Число 6 передается для параметра m во внутреннюю функцию (int m) => n * m;
Также мы можем использовать другой вариант для вызова замыкания:
fun main() { val result1 = multiply(5)(6) // 30 println(result1) // 30 val result2 = multiply(5)(5) // 25 println(result2) // 25 } fun multiply(n:Int): (Int)->Int{ return {m:Int-> n * m} }