Для чего нужны лямбда выражения. Лямбда-функции и замыкания. Передача параметров в лямбда-выражение

Высокоуровневая функция - это функция, которая принимает другую функцию в качестве входного аргумента, либо имеет функцию в качестве возвращаемого результата. Хорошим примером такой функции является lock() , которая берёт залоченный объект и функцию, применяет лок, выполняет функцию и отпускает lock:

Fun lock(lock: Lock, body: () -> T): T{ lock.lock() try{ return body() } finally { lock.unlock() } }

Определение

Последним пунктом в этом кратком пошаговом примечании является функциональный интерфейс, который является интерфейсом с единственным методом, используемым в качестве типа выражения лямбда. Лямбда-выражение представляет собой анонимную функцию, которая обеспечивает краткий и функциональный синтаксис, который используется для написания анонимных методов. Он основан на концепции программирования функций и используется для создания делегатов или типов дерева выражений. Синтаксис - это выражение функции.

Как правило, лямбда-выражения не используются непосредственно в синтаксисе запроса, но часто используются в вызовах методов. Выражения запроса могут содержать вызовы методов. Может содержать вызов функциональной процедуры, но не может содержать вызов подпроцедуры. Значение, возвращаемое функцией, является только значением выражения, содержащегося в теле функции. Параметры должны иметь указанные типы данных или быть выведены. Не разрешает общие параметры.

  • Это функция без имени.
  • Модификаторов нет, таких как перегрузки и переопределения.
  • Тело функции должно содержать выражение, а не выражение.
Лямбда-выражения обеспечивают сокращение для компилятора, позволяя ему испускать методы, назначенные делегатам.

Давайте проанализируем этот блок. Параметр body имеет функциональный тип: () -> T , то есть предполагается, что это функция, которая не имеет никаких входных аргументов и возвращает значение типа T . Она вызывается внутри блока try , защищена lock , и её результат возвращается функцией lock() .

Fun toBeSynchronized() = sharedResource.operation() val result = lock (lock, ::toBeSynchronized)

Подчеркивание для неиспользуемых переменных

Компилятор выполняет автоматический вывод типа по аргументам лямбда, что является ключевым преимуществом. Функция более высокого порядка - это функция, которая выполняет функции как параметры или возвращает функцию. Если параметр лямбда не используется, вы можете поместить знак подчеркивания вместо его имени.

Разрушение в Лямбдасе

Разрушение в лямбдах описывается как часть деклараций деструктурирования. Иногда полезно повысить производительность функций более высокого порядка, используя встроенные функции.

Лямбда-выражения и анонимные функции

Выражение лямбда или анонимная функция является «функциональным литералом», то есть функцией, которая не объявлена, а передается сразу как выражение. Рассмотрим следующий пример.

Другой, наиболее удобный способ применения лямбда-выражения :

Val result = lock(lock, { sharedResource.operation() })

Лямбда-выражения более подробно описаны , но в целях продолжить этот раздел, давайте произведём краткий обзор:

  • Лямбда-выражения всегда заключены в фигурные скобки,
  • Параметры этого выражения (если такие есть) объявлены до знака -> (параметры могут быть опущены),
  • Тело выражения идёт после знака -> .

В Kotlin существует конвенция, по которой, если последний параметр функции является функцией, и вы применяете лямбда- выражение в качестве аргумента, вы можете указать её вне скобок:

Максимум. Этот второй аргумент является выражением, которое само является функцией, то есть функциональным литералом. Как функция, она эквивалентна. Чтобы функция могла принимать другую функцию в качестве параметра, мы должны указать тип функции для этого параметра.

Тип функции записывается, как указано выше, или может иметь именованные параметры, если вы хотите задокументировать значение каждого параметра. Чтобы объявить переменную с нулевым значением типа функции, в круглые скобки заключите весь тип функции и поместите знак вопроса после нее.

Lock (lock) { sharedResource.operation() }

Другим примером функции высшего порядка служит функция map() :

Fun List.map(transform: (T) -> R): List { val result = arrayListOf() for (item in this) result.add(transform(item)) return result }

Эта функция может быть вызвана следующим образом:

Val doubled = ints.map { it -> it * 2 }

Обратите внимание, что параметры могут быть проигнорированы при вызове функции в том случае, если лямбда является единственным аргументом для её вызова.

Введение в Lambda-выражения

Полная синтаксическая форма лямбда-выражений, т.е. литералов типов функций, такова. Если выведенный тип возврата лямбда не является Единицей, последнее выражение внутри тела лямбда рассматривается как возвращаемое значение. Если оставить все необязательные аннотации, то, что осталось, выглядит так.

Что это за функции такие: лямбда и замыкание?

Очень распространено, что выражение лямбда имеет только один параметр. Мы можем явно вернуть значение из лямбда с использованием квалифицированного синтаксиса возврата. В противном случае значение последнего выражения неявно возвращается. Поэтому два следующих фрагмента эквивалентны.

Ключевое слово it: неявное имя единственного параметра

Ещё одной полезной особенностью синтаксиса является возможность опустить объявление параметра функции в случае, если он единственный (вместе с ->). Слово it будет принято в качестве имени для такой функции:

Ints.map { it * 2 }

Это соглашение позволяет писать код в LINQ стиле:

Обратите внимание: если функция принимает в качестве последнего параметра другую функцию, аргумент выражения лямбда может быть передан вне списка аргументов в скобках. Одна из недостатков синтаксиса лямбда-выражения, представленная выше, - это возможность указать возвращаемый тип функции. В большинстве случаев это необязательно, потому что тип возврата может быть выведен автоматически. Однако, если вам нужно явно указать его, вы можете использовать альтернативный синтаксис: анонимная функция.

Анонимная функция очень похожа на объявление регулярной функции, за исключением того, что ее имя опущено. Его тело может быть выражением или блоком. Параметры и тип возврата указаны так же, как и для обычных функций, за исключением того, что типы параметров могут быть опущены, если они могут быть выведены из контекста.

Strings.filter { it.lenght == 5 }.sortBy { it }.map { it.toUpperCase() }

Инлайн функции

Иногда необходимо улучшить производительность высокоуровневых функций, используя инлайн функции .

Лямбда-выражения и анонимные функции

Лямбда-выражения или анонимные функции являются "функциональными константами" (ориг. "functional literal") , то есть функциями, которые не были объявлены, но сразу были переданы в качестве выражения. Рассмотрим следующий пример:

Вывод типа возвращаемого значения для анонимных функций работает так же, как и для обычных функций: тип возврата выводится автоматически для анонимных функций с телом выражения и должен быть явно указан для анонимных функций с блочным телом. Обратите внимание, что анонимные параметры функции всегда передаются внутри круглых скобок. Сокращенный синтаксис, позволяющий оставить функцию вне круглых скобок, работает только для лямбда-выражений.

Другим различием между лямбда-выражениями и анонимными функциями является поведение нелокальных возвратов. Это означает, что возврат внутри выражения лямбда будет возвращаться из закрывающей функции, тогда как возврат внутри анонимной функции будет возвращаться из анонимной функции. Лямбда-выражение или анонимная функция могут получить доступ к его замыканию, то есть переменные, объявленные во внешней области.

Max(strings, { a, b -> a.length < b.length })

Функция max - высокоуровневая функция, так как она принимает другую функцию в качестве входного аргумента. Этот второй аргумент является выражением, которое само по себе представляет из себя функцию, то есть functional literal .

Fun compare(a: String, b: String): Boolean = a.length < b.length

Типы функций

Для того, чтобы функция принимала другую функцию в качестве входного параметра, нам необходимо указать её (входящей функции) тип. К примеру, вышеуказанная функция max определена следующим образом:

Функциональные литералы с приемником

Это похоже на функции расширения, которые позволяют вам обращаться к членам объекта-получателя внутри тела функции. Тип такого литерала функции - это тип функции с приемником. Функциональный литерал можно вызвать, как если бы это был метод на объекте получателя.

Синтаксис анонимных функций позволяет напрямую указать тип получателя литерала функции. Это может быть полезно, если вам нужно объявить переменную типа функции с приемником и использовать ее позже. Лямбда-выражения могут использоваться как литералы функций с приемником, когда тип приемника может быть выведен из контекста.

Fun max(collection: Collection, less: (T, T) -> Boolean): T? { var max: T? = null for (it in collection) if (max == null || less(max, it)) max = it return max }

Параметр "less" является (T, T) -> Boolean типом, то есть функцией, которая принимает два параметра типа T и возвращает "Boolean":"true", если первый параметр меньше, чем второй.

В теле функции, строка 4, less используется в качестве функции: она вызывается путём передачи двух аргументов типа T .

Предпочитают стандартные функциональные интерфейсы

В этом уроке мы рассмотрим функциональные интерфейсы и лямбда-выражения. Каждый из этих интерфейсов является общим и абстрактным, что позволяет легко адаптироваться практически к любому выражению лямбда. Разработчики должны изучить этот пакет перед созданием новых функциональных интерфейсов. Чтобы выполнить его, вы должны написать.

Использовать переменные «Эффективно конечные»

Чтобы выполнить это, мы можем написать. Это не всегда короче, но делает код более удобочитаемым. Доступ к не конечной переменной в лямбда-выражениях приведет к ошибке времени компиляции. В соответствии с концепцией «фактически окончательный» компилятор рассматривает каждую переменную как окончательную, если она назначается только один раз.

Тип функции может быть написан так, как указано выше, или же может иметь определённые параметры, если вы хотите обозначить значения каждого из параметров.

Val compare: (x: T, y: T) -> Int = ...

Синтаксис лямбда-выражений

Полная синтаксическая форма лямбда-выражений, таких как literals of function types , может быть представлена следующим образом:

Защита переменных объекта от мутации

Например, следующий код не будет компилироваться. Компилятор сообщит вам, что. Этот подход должен упростить процесс выполнения лямбда-исполнения без резьбы. Одной из основных целей лямбда является использование в параллельных вычислениях - это означает, что они действительно полезны, когда дело касается безопасности потоков.

Лямбда-выражения имеют методы и возможные значения параметра и возврата. Лямбда-выражения могут быть написаны по-разному, так как существуют аббревиатуры для различных конструкций. Однако форма, которая всегда применяется, такова. Имя параметра открывает новую область для переменной. Имя параметра не может переопределять любые другие имена локальных переменных. Здесь переменная параметра лямбда ведет себя как новая переменная из внутреннего блока, а не переменная из внутреннего класса, где видимость отличается.

Val sum = { x: Int, y: Int -> x + y }

Лямбда-выражение всегда заключено в скобки {...} , объявление параметров при таком синтаксисе происходит внутри этих скобок и может включать в себя аннотации типов (опционально), тело функции начинается после знака -> . Если тип возвращаемого значения не Unit , то в качестве возвращаемого типа принимается последнее (а возможно и единственное) выражение внутри тела лямбды.

Если для компилятора доступно достаточно информации о типе, компилятор разрешает аббревиатуру для лямбда-выражений. Вывод типа прост, поэтому работает следующая аббревиатура. Список параметров содержит либо явно объявленные типы параметров, либо неявные выводимые типы. Смесь не допускается, компилятор блокирует что-то вроде или с ошибкой.

Если компилятор может читать типы, типы параметров являются необязательными. Но вывод типа не всегда возможен, поэтому аббревиатура не всегда возможна. Кроме того, явное правописание помогает читать: короткие выражения не обязательно являются наиболее понятными.

Если мы вынесем все необязательные объявления, то, что останется, будет выглядеть следующим образом:

Val sum: (Int, Int) -> Int = { x, y -> x + y }

Обычное дело, когда лямбда-выражение имеет только один параметр. Если Kotlin может определить сигнатуру метода сам, он позволит нам не объявлять этот единственный параметр, и объявит его сам под именем it:

Аббревиатура 2: Лямбда-корпус представляет собой либо одно выражение, либо блок

Компилятор читает из типов, независимо от того, присутствуют ли все свойства. Типы являются явными или неявными. Если тело лямбда-выражения состоит только из одного выражения, сокращенная нотация может сохранять скобки блока и точку с запятой. Полная и сокращенная нотация.

То есть, если лямбда-выражения используются с синтаксисом коротких выражений, эти выражения могут возвращать что-то, но не нужно. Написание фигурных скобок и обратных выражений нельзя смешивать. Либо есть блок сгибаемых круглых скобок, либо возврат или отсутствие круглых скобок и ключевое слово возврата. Поэтому ошибки приводят к этим неправильным смесям.

Ints.filter { it > 0 } //Эта константа имеет тип "(it: Int) -> Boolean"

Мы можем явно вернуть значение из лямбды, используя qualified return синтаксис:

Ints.filter { val shouldFilter = it > 0 shouldFilter } ints.filter { val shouldFilter = it > 0 return@filter shouldFilter }

Обратите внимение, что функция принимает другую функцию в качестве своего последнего параметра, аргумент лямбда-выражения в таком случае может быть принят вне списка аргументов, заключённого в скобках. См. callSuffix .

Аббревиатура 3: индивидуальный идентификатор вместо списка параметров и круглых скобок

Если бы мы использовали явный возврат, все было бы хорошо, возвращение было бы также компиляцией. Имеют ли лямбда-выражения возврат, выражающий два выражения. Скобки могут быть опущены. Различные степени аббревиатур. Любой, кто слышит термин «данные», сначала думает о числах, байтах, символьных строках или даже о сложных объектах со своим состоянием. В этой главе мы хотим немного расширить это представление и направить его на программный код. И как только мы доберемся до этой точки зрения, что код равен данным, код может передаваться как данные, переносятся с одной точки на другую, сохраняя и позже ссылаясь.

Анонимные функции

Единственной особенностью синтаксиса лямбда-выражений, о которой ещё не было сказано, является способность определять и назначать возвращаемый функцией тип. В большинстве случаев в этом нет особой необходимости, потому что он может быть вычислен автоматически. Однако, если у вас есть потребность в определении возвращаемого типа, вы можете воспользоваться альтернативным синтаксисом:

Отличие Lambda-выражений от анонимных класов

С этой возможностью передачи кода поведение алгоритмов может быть легко адаптировано. Начнем с нескольких примеров, где передается программный код, который затем открывается позже. Для сортировки данных может быть определен отдельный порядок, который может быть передан сортировщику в качестве компаратора. Компаратор объявляет метод сравнения, к которому применяется сортировщик, чтобы привести два объекта в желаемый порядок.

  • Поток выполняет программный код в фоновом режиме.
  • Если пользователь активирует кнопку на интерфейсе, это приводит к действию.
Объект этого класса передается в другое место, и заинтересованная сторона затем использует этот метод для доступа к программному коду.

Fun(x: Int, y: Int): Int = x + y

Объявление анонимной функции выглядит очень похоже на обычное объявление функции, за исключением того, что её имя опущено. Тело такой функции может быть описано и выражением (как показано выше), и блоком:

Fun(x: Int, y: Int): Int { return x + y }

Параметры функции и возвращаемый тип обозначаются таким же образом, как в обычных функциях. Правда, тип параметра может быть опущен, если его значение следует из контекста:

Ints.filter(fun(item) = item > 0)

Аналогично и с типом возвращаемого значения: он вычисляется автоматически для функций-выражений или же должен быть определён вручную (если не является типом Unit) для анонимных функций, которые имеют в себе блок.

Обратите внимание, что параметры анонимных функций всегда заключены в скобки {...} . Приём, позволяющий оставлять параметры вне скобок, работает только с лямбда-выражениями.

Одним из отличий лямбда-выражений от анонимных функций является поведение оператора return (non-local returns). Слово return , не имеющее метки (@), всегда возвращается из функции, объявленной ключевым словом fun . Это означает, что return внутри лямбда-выражения возвратит выполнение к функции, включающей в себя это лямбда-выражение. Внутри анонимных функций оператор return , в свою очередь, выйдет, собственно, из анонимной функции.

Замыкания

Лямбда-выражение или анонимная функция (так же, как и локальная функция или object expression) имеет доступ к своему замыканию, то есть к переменным, объявленным вне этого выражения или функции. В отличае от Java, переменные, захваченные в замыкании, могут быть изменены:

Var sum = 0 ints.filter { it > 0 }.forEach { sum += it } print(sum)

Литералы функций с объектом-приёмником

Kotlin предоставляет возможность вызывать литерал функции с указаным объектом-приёмником. Внутри тела литерала вы можете вызывать методы объекта-приёмника без дополнительных определителей. Это схоже с принципом работы расширений , которые позволяют получить доступ к членам объекта-приёмника внутри тела функции. Один из самых важных примеров использования литералов с объектом-приёмником это Type-safe Groovy-style builders .

Тип такого литерала - это тип функции с приёмником:

Sum: Int.(other: Int) -> Int

По аналогии с расширениями, литерал функции может быть вызван так, будто он является методом объекта-приёмника:

1.sum(2)

Синтаксис анонимной функции позволяет вам явно указать тип приёмника. Это может быть полезно в случае, если вам нужно объявить переменную типа нашей функции для использования в дальнейшем.

Val sum = fun Int.(other: Int): Int = this + other

Лямбда-выражения могут быть использованы как литералы функций с приёмником, когда тип приёмника может быть выведен из контекста.

Class HTML { fun body() { ... } } fun html(init: HTML.() -> Unit): HTML { val html = HTML() // создание объекта-приёмника html.init() // передача приёмника в лямбду return html } html { // лямбда с приёмником начинается тут body() // вызов метода объекта-приёмника }

Лямбда-выражения представляют упрощенную запись анонимных методов. Лямбда-выражения позволяют создать емкие лаконичные методы, которые могут возвращать некоторое значение и которые можно передать в качестве параметров в другие методы.

Ламбда-выражения имеют следующий синтаксис: слева от лямбда-оператора => определяется список параметров, а справа блок выражений, использующий эти параметры: (список_параметров) => выражение. Например:

Class Program { delegate int Square(int x); // объявляем делегат, принимающий int и возвращающий int static void Main(string args) { Square squareInt = i => i * i; // объекту делегата присваивается лямбда-выражение int z = squareInt(6); // используем делегат Console.WriteLine(z); // выводит число 36 Console.Read(); } }

Здесь i => i * i представляет лямбда-выражение, где i - это параметр, а i*i - выражение.

При использовании надо учитывать, что каждый параметр в лямбда-выражении неявно преобразуется в соответствующий параметр делегата, поэтому типы параметров должны быть одинаковыми. Кроме того, количество параметров должно быть таким же, как и у делегата. И возвращаемое значение лямбда-выражений должно быть тем же, что и у делегата.

Возьмем класс Account из прошлой темы и перепишем прикрепление обработчика события с помощью лямбды-выражения:

Account account = new Account(200, 6); account.Added += (sender, e)=> { Console.WriteLine("Сумма транзакции: {0}", e.sum); Console.WriteLine(e.message); };

Поскольку здесь используется несколько параметров, то они заключаются в скобки. И так как в теле лямбда-выражения применяется несколько выражений, то они заключаются в блок из фигурных скобок.

Бывает, что параметров не требуется. В этом случае вместо параметра в лямбда-выражении используются пустые скобки:

Class Program { delegate void message(); // делегат без параметров static void Main(string args) { message GetMessage = () => { Console.WriteLine("Лямбда-выражение"); }; GetMessage(); Console.Read(); } }

Также лямбда-выражение необязательно должно принимать блок операторов и выражений. Оно может также принимать ссылку на метод:

Class Program { delegate void message(); // делегат без параметров static void Main(string args) { message GetMessage = () => Show_Message(); GetMessage(); } private static void Show_Message() { Console.WriteLine("Привет мир!"); } }

Как и делегаты, лямбда-выражения можно передавать в качестве параметров методу. Нередко лямбды в качестве параметров и используются, что довольно удобно:

Class Program { delegate bool IsEqual(int x); static void Main(string args) { int integers = { 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // найдем сумму чисел больше 5 int result1 = Sum(integers, x => x > 5); Console.WriteLine(result1); // 30 // найдем сумму четных чисел int result2 = Sum(integers, x => x % 2 == 0); Console.WriteLine(result2); //20 Console.Read(); } private static int Sum (int numbers, IsEqual func) { int result = 0; foreach(int i in numbers) { if (func(i)) result += i; } return result; } }

Метод Sum принимает в качестве параметра массив чисел и делегат IsEqual и возвращает сумму чисел массива в виде объекта int. В цикле проходим по всем числам и складываем их. Причем складываем только те числа, для которых делегат IsEqual func возвращает true. То есть делегат IsEqual здесь фактически задает условие, которому должны соответствовать значения массива. Но на момент написания метода Sum нам неизвестно, что это за условие.

При вызове метода Sum ему передается массив и лямбда-выражение:

Int result1 = Sum(integers, x => x > 5);

То есть параметр x здесь будет представлять число, которое передается в делегат:

If (func(i))

А выражение x > 5 представляет условие, которому должно соответствовать число. Если число соответствует этому условию, то лямбда-выражение возвращает true, а переданное число складывается с другими числами.

Подобным образом работает второй вызов метода Sum, только здесь уже идет проверка числа на четность, то есть если остаток от деления на 2 равен нулю:

Int result2 = Sum(integers, x => x % 2 == 0);