Kotlin Learning — функции высшего порядка и встроенные функции

Определение функции высшего порядка

Функция называется функцией высшего порядка, если она принимает другую функцию в качестве аргумента или возвращает функцию другого типа.

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

тип функции

Структура заявления:

(String, Int) -> Unit 

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

->Часть справа используется для объявления типа возвращаемого значения функции, и оно будет использоваться, если возвращаемое значение отсутствует Unit, что эквивалентно тому, что используется в Java void.

пример

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

1. Примеры функций высшего порядка

fun numOperation(num1: Int, num2: Int, operationFunc: (Int, Int) -> Int): Int {
    
    
    return operationFunc(num1, num2)
}

fun plus(num1: Int, num2: Int): Int {
    
    
    return num1 + num2
}

fun minus(num1: Int, num2: Int): Int {
    
    
    return num1 - num2
}

Выше объявлена ​​функция, которая работает с числами numOperation, и передается входной параметр функционального типа (Int, Int) -> Intдля работы с ранее введенными числами. Последняя plusсумма minusопределяет две функции для цифровых операций, см. использование ниже.

fun main() {
    
    
    val plusResult = numOperation(1, 3, ::plus)
    val minusResult = numOperation(7, 2, ::minus)
    println("plusResult:$plusResult | minusResult:$minusResult")
}

//运行结果
plusResult:4 | minusResult:5

::plusИ ::minusэто способ написания ссылки на функцию, и конкретная логика работы определяется параметром типа передаваемой функции.

2. Лямбда-выражение

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

fun main() {
    
    
    val plusResult1 = numOperation(2, 3) {
    
     n1, n2 -> n1 + n2 }
    val plusResult2 = numOperation(5, 2) {
    
     n1, n2 -> n1 - n2 }
    println("plusResult1:$plusResult1 | plusResult2:$plusResult2")
}
//运行结果
plusResult1:5 | plusResult2:3

Гораздо проще переключиться на лямбда-выражения. Все мы знаем, что код Kotlin в конечном итоге будет скомпилирован в байт-код Java, но в Java нет концепции функций высшего порядка. Так как же реализуются функции высшего порядка? Посмотрим на Java-код после декомпиляции байт-кода:

public static final void main() {
    
    
	int plusResult1 = numOperation(2, 3, (Function2)null.INSTANCE);
	int plusResult2 = numOperation(5, 2, (Function2)null.INSTANCE);
}

public static final int numOperation(int num1, int num2, @NotNull Function2 operationFunc) {
    
    
	Intrinsics.checkNotNullParameter(operationFunc, "operationFunc");
	return ((Number)operationFunc.invoke(num1, num2)).intValue();
}

Во-первых, давайте объясним Function2.Kotlin внутренне определяет серию интерфейсов FunctionX в файле Function.kt.Лямбда-выражения (анонимные функции), определенные разработчиками, будут реализованы путем реализации интерфейсов FunctionX в нижней части Kotlin.

Определение того, какой интерфейс FunctionX использовать, основано на количестве входных параметров и возвращаемых значений, определенных в Lambda. Поскольку у нас есть два входных параметра и одно возвращаемое значение, это Function2.

public interface Function1<in P1, out R> : Function<R> {
    
    
	public operator fun invoke(p1: P1): R
}

public interface Function2<in P1, in P2, out R> : Function<R> {
    
    
    public operator fun invoke(p1: P1, p2: P2): R
}

Давайте посмотрим на значение, переданное при вызове (Function2)null.INSTANCE, которое на самом деле является экземпляром анонимного внутреннего класса типа интерфейса Function2.

В JVM выражения Lambda существуют в виде экземпляров объектов (анонимных внутренних классов), и JVM будет выделять память для всех переменных, которые работают с Lambda. Поэтому, хотя лямбда-выражения просты в использовании, их реализация неблагоприятна для памяти и производительности.

Чтобы решить эту проблему, Kotlin предоставляет функциональность встроенных функций.

Встроенная функция

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

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

inline fun numOperation(num1: Int, num2: Int, operationFunc: (Int, Int) -> Int): Int {
    
    
    return operationFunc(num1, num2)
}

Посмотрите еще раз на код Java после декомпиляции байт-кода:

public static final void main() {
    
    
     byte num1$iv = 2;
     int num1$iv = 3;
     int $i$f$numOperation = false;
     int var6 = false;
     //plus函数替换
     int plusResult1 = num1$iv + num1$iv;
     num1$iv = 5;
     int num2$iv = 2;
     int $i$f$numOperation = false;
     int var7 = false;
     //minus函数替换
     int plusResult2 = num1$iv - num2$iv;
     String var9 = "plusResult1:" + plusResult1 + " | plusResult2:" + plusResult2;
     $i$f$numOperation = false;
     System.out.println(var9);
}

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

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

ключевое слово

Если функция высокого порядка получает два или более параметра типа функции, и мы добавляем inlineв функцию ключевые слова, компилятор Kotlin автоматически встраивает все лямбда-выражения, на которые ссылаются.

Если мы хотим встроить только одно из лямбда-выражений, мы можем использовать noinlineключевые слова, как показано в формате синтаксиса:

inline fun inlineTest(block1: () -> Unit, noinline block2: () -> Unit) {
    
    
} 

При этом будет встроено только лямбда-выражение, на которое ссылается параметр block1.

1. Смысл появления ключевого слова noinline

Говорят, что преимущества встроенных функций были изучены раньше, так почему же Kotlin предоставляет ключевое слово noinline для исключения встроенных функций?

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

Приведенный выше отрывок взят из третьего издания «Первой строки кода» Го Линя. Честно говоря, я первый раз дочитал, так и не понял, что это значит, узнал только написав код и опробовав его, далее давайте сначала перейдем к коду:

fun numOperation(num1: Int, num2: Int, operationFunc: (Int, Int) -> Int): Int {
    
    
    return operationFunc(num1, num2)
}

fun func1(operationFunc: (Int, Int) -> Int) {
    
    
    var a = 3;
    var b = 2;
    println("func1 result: " + operationFunc(a, b))
}

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

fun numOperation(num1: Int, num2: Int, operationFunc: (Int, Int) -> Int): Int {
    
    
 	func1(operationFunc)
    return operationFunc(num1, num2)
}

fun func1(operationFunc: (Int, Int) -> Int) {
    
    
    var a = 3;
    var b = 2;
    println("func1 result: " + operationFunc(a, b))
}

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

inline fun numOperation(num1: Int, num2: Int, operationFunc: (Int, Int) -> Int): Int {
    
    
     //func1调用报红
 	func1(operationFunc)
    return operationFunc(num1, num2)
}

fun func1(operationFunc: (Int, Int) -> Int) {
    
    
    var a = 3;
    var b = 2;
    println("func1 result: " + operationFunc(a, b))
}

После добавления inlineключевого слова func1он сообщает красный цвет и выдает сообщение об ошибке: Недопустимое использование встроенного параметра «operationFunc» в «общедоступном встроенном веселье numOperation (num1: Int, num2: Int, operationFunc: (Int, Int) -> Int): Int определено в com.jane.kotlinlearning.test в файле Test3.kt. Добавьте модификатор noinline в объявление параметра. Подсказки должны быть noinlineиндивидуально украшены ключевыми словами.

Независимо от этой подсказки, давайте попробуем func1добавить inlineключевые слова:

inline fun numOperation(num1: Int, num2: Int, operationFunc: (Int, Int) -> Int): Int {
    
    
 	func1(operationFunc)
    return operationFunc(num1, num2)
}

inline fun func1(operationFunc: (Int, Int) -> Int) {
    
    
    var a = 3;
    var b = 2;
    println("func1 result: " + operationFunc(a, b))
}

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

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

Подводить итоги:

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

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

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

inline fun numOperation(num1: Int, num2: Int, noinline operationFunc: (Int, Int) -> Int
						, print: (Int, Int) -> Unit): Int {
    
    
    print(num1,num2)
 	func1(operationFunc)
    return operationFunc(num1, num2)
}

fun func1(operationFunc: (Int, Int) -> Int) {
    
    
    var a = 3;
    var b = 2;
    println("func1 result: " + operationFunc(a, b))
}

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

noinlineРоль ключевых слов заключается в реализации локального встраивания и передачи параметров функции.

2. returnВозврат разницы

Выше было много сказано о noinlineсмысле существования. Давайте посмотрим на разницу между встроенной функцией и возвратом не встроенной функции return.

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

Во встроенной функции это эквивалентно копированию копии кода в лямбда-выражении в основную функцию и становится частью основной функции, поэтому напрямую основная функция остановится и вернётся напрямую return.

Посмотрите на код ниже:

fun printContent(content: String, printFunc: (String) -> Unit) {
    
    
    println("printContent start")
    printFunc(content)
    println("printContent end")
}

fun main() {
    
    
    println("main start")
    var param = ""
    printContent(param) {
    
     s ->
        println("lambda start")
        if (s.isEmpty()) return@printContent
        println(s)
        println("lambda end")
    }
    println("main end")
}

В Kotlin невозможно вернуться непосредственно в лямбда-выражении, и будет сообщено об ошибке: «возврат» здесь не разрешен, поэтому использование в приведенном выше коде означает частичный возврат в return@printStringлямбда-выражении. За @ следует имя вызываемой функции.Обратите внимание, что перед return и @ нет пробела. Функции высшего порядка в текущем примере не встроены, вывод результата:

main start
printContent start
lambda start
printContent end
main end

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

Измените код, чтобы добавить встроенные ключевые слова.

inline fun printContent(content: String, printFunc: (String) -> Unit) {
    
    
    println("printContent start")
    printFunc(content)
    println("printContent end")
}

fun main() {
    
    
    println("main start")
    var param = ""
    printContent(param) {
    
     s ->
        println("lambda start")
        if (s.isEmpty()) return
        println(s)
        println("lambda end")
    }
    println("main end")
}

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

main start
printContent start
lambda start

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

public static final void main() {
    
    
      String param = "main start";
      boolean $i$f$printContent = false;
      System.out.println(param);
      //入参赋值为空串
      param = "";
      $i$f$printContent = false;
      String var2 = "printContent start";
      boolean var3 = false;
      System.out.println(var2);
      int var5 = false;
      String var6 = "lambda start";
      boolean var7 = false;
      System.out.println(var6);
      //var10 变量也是空字符串
      CharSequence var10 = (CharSequence)param;
      var7 = false;
      //判断无法进入
      if (var10.length() != 0) {
    
    
         boolean var11 = false;
         System.out.println(param);
         var6 = "lambda end";
         var7 = false;
         System.out.println(var6);
         var2 = "printContent end";
         var3 = false;
         System.out.println(var2);
         String var8 = "main end";
         boolean var9 = false;  
         System.out.println(var8);
     }
}   

Поскольку param = ""оператор if не может войти, основная функция mainзавершается напрямую.

ключевое слово

Сначала посмотрите на код:

fun main() {
    
    
	var param = ""
    printContent(param) {
    
     s ->
	    if (s.isEmpty()) return
	    println(s)
    }
}

inline fun printContent(content: String, printFunc: (String) -> Unit) {
    
    
	val runnable = Runnable {
    
    
	    //下面这句代码报红
        printFunc(content)
    }
}

Я удалил всю печать в приведенном выше примере, чтобы код был понятнее. Здесь я изменяю его, сначала создаю анонимный объект Runnable внутреннего класса во встроенной функции, а затем запускаю printFunc в объекте.

Приведенный выше компилятор кода выдает ошибку: Невозможно встроить здесь printFunc: он может содержать нелокальные возвраты Добавьте модификатор crossinline в объявление параметра printFunc.

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

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

Вот смотрим:

1. В лямбда-выражениях нельзя использовать return, если лямбда-выражение не является параметром встроенной функции.

2. Параметры типа функции не разрешены в анонимных классах во встроенных функциях.

Если это так, нет ли способа использовать встроенные функции? Как это возможно?Встроенная функция настолько хороша, что появляются ключевые слова, подсказанные компилятором выше crossinline. Добавить попытку:

fun main() {
    
    
	var param = "aaa"
    printContent(param) {
    
     s ->
	    //if (s.isEmpty()) return
	    println(s)
    }
}

inline fun printContent(content: String, crossinline printFunc: (String) -> Unit) {
    
    
	val runnable = Runnable {
    
    	  
        printFunc(content)
    }
}

Я изменил два места в приведенном выше коде: во-первых, я удалил returnоператор, во-вторых, я добавил crossinlineключевое слово перед входным параметром функции.

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

А что, если лямбда-выражение хочет выйти локально? Мы можем использовать @printContentобозначение для частичного возврата. Все операции, которые вы хотите, сделаны.

Supongo que te gusta

Origin blog.csdn.net/kongqwesd12/article/details/131439380
Recomendado
Clasificación