Kotlin coroutine framework involves many keywords and syntactic sugar, among which higher-order functions and inline functions are used in many places

1. Higher-order functions

A higher-order function is a function (or method) that is passed as a parameter value to another function and abbreviated using the lambda expression property. The function can have no arguments or return values, as well as higher-order functions

For example, define a higher-order function that prints log

Case 1

private fun logd(msg: () -> String){
   Log.d("LOG"."> > >${msg()}")}Copy the code

MSG: () -> String specifies a higher-order function, and MSG specifies the name of the higher-order function. () is a parameter declaration for this higher-order function. Currently, it has no parameters. Any parameters can be written as (a: String, b: Int) or (String, Int). -> String is The return value of The higher-order function. The arrow points to The object type of The higher-order function. It is currently a return String, and no return value can be written as -> Unit (Unit). the Unit object. This type corresponds to the void type in Java. Void = void = void = void

Print log for higher-order function calls:

logd { "Test" }
Copy the code

Calling higher-order functions is abbreviated by lambda expressions. Kotlin has a convention: When the function has only one passing parameter and the parameter is a higher-order function, the abbreviation can be omitted (); This parameter can be specified outside () when a function passes more than one argument but the last one is a higher-order function

Case 2

/ / define
private fun logd(tag: String, msg: () -> String){
   Log.d(tag, "> > >${msg()}")}/ / call
logd("LOG") {"Test"
}
Copy the code

conclusion

  • It is not unusual to pass a function (or method) as an argument. Java can pass a function that returns a value as an argument, but Java executes this function first. Unlike kotlin’s higher-order functions, this does not take place immediately. The Kotlin coroutine framework is designed according to this language feature. The Kotlin coroutine framework can be said to be a framework created by language features

  • The disadvantage is that higher-order functions are compiled into anonymous interface classes, which are created every time they are called. The overhead of the class is very high and needs to be solved by using inline functions (examples of generating anonymous classes are described in more detail in inline functions).

The coroutine framework uses higher-order functions in many places, including: launch function and async function:

/ / launch function
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope. () - >Unit
): Job {···}/ / async function
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope. () - >T
): Deferred<T> {···}Copy the code

Mount the function to a coroutine for execution

2. Inline functions
Function methods that are decorated with the inline keyword are called inline functionsCopy the code

The role of inline functions

(1) Prevent high-order functions from decompiling into anonymous classes and reduce class creation overhead

Example 3

class InlineTest {

    fun test(a){
        logd {
            "Test"
        }

        logi {
            "Test"}}private fun logd(msg: () -> String){
        Log.d("LOG"."> > >${msg()}")}inline fun logi(msg: () -> String){
        Log.i("LOG"."> > >${msg()}")}}Copy the code

Decompilation result:

@Metadata( mv = {1, 1, 16}, bv = {1, 0, 3}, k = 1, d1 = {"\u0000\u001e\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\n\u0002\u0018\u0002\n \ u0002 \ u0010 \ u000e \ n \ u0002 \ b \ u0003 \ u0018 \ u00002 \ u00020 \ u0001B \ u0005 ¢\ u0006 \ u0002 \ u0010 \ u0002J \ u0016 \ u0010 \ u0003 \ u001a \ u0 0020\u00042\f\u0010\u0005\u001a\b\u0012\u0004\u0012\u00020\u00070\u0006H\u0002J\u0017\u0010\b\u001a\u00020\u00042\f\u001 0 \ u0005 \ u001a \ \ b u0012 \ u0004 \ u0012 \ u00020 \ u00070 \ u0006H \ u0086 bJ \ u0006 \ u0010 \ \ t \ u001a \ u00020 \ u0004 ¨ \ u0006 \ n "}, d2 = {"Lcom/wxk/coroutines/InlineTest;" , "", "()V", "logd", "", "msg", "Lkotlin/Function0;" , "", "logi", "test", "app"} )
public final class InlineTest {
   public final void test(a) {
      this.logd((Function0)null.INSTANCE);
      int $i$f$logi = false;
      StringBuilder var5 = (new StringBuilder()).append("> > >");
      String var4 = "LOG";
      int var3 = false;
      String var6 = "Test";
      Log.i(var4, var5.append(var6).toString());
   }

   private final void logd(Function0 msg) {
      Log.d("LOG"."> > >" + (String)msg.invoke());
   }

   public final void logi(@NotNull Function0 msg) {
      int $i$f$logi = 0;
      Intrinsics.checkParameterIsNotNull(msg, "msg");
      Log.i("LOG"."> > >"+ (String)msg.invoke()); }}Copy the code

Function0:

/** A function that takes 0 arguments. */
public interface Function0<out R> : Function<R> {
    /** Invokes the function. */
    public operator fun invoke(a): R
}
Copy the code

The decompilated code is obvious. The logd() method is not inline, but instead creates an anonymous interface class called Function0. The logi() method, modified with the inline keyword, copies code into the test() method, creating an anonymous class without the inline keyword

(2) Eliminate the performance loss caused by frequently calling the same method in and out of the stack

Example 4

class InlineTest {

    fun test(a){

        for (i in 1.3.){
            logd()
        }

        for (i in 1.3.){
            logi()
        }

    }

    private fun logd(a){
        Log.d("LOG".">>> logd")}inline fun logi(a){
        Log.i("LOG".">>> logi")}}Copy the code

Decompilation result:

@Metadata( mv = {1, 1, 16}, bv = {1, 0, 3}, k = 1, d1 = {"\u0000\u0014\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0002\b\u0003\u0018\u00002\ U00020 \ u0001B \ u0005 ¢\ u0006 \ u0002 \ u0010 \ \ u0010 \ u0003 u0002J \ b \ u001a \ u00020 \ u0004H \ u0002J \ t \ u0010 \ u0005 \ u001a \ u00020 \ u0004H BJ \ u0006 \ u0010 \ \ u0086 \ u0006 \ u001a \ u00020 \ u0004 ¨ \ u0006 \ u0007 "}, d2 = {" Lcom/WXK/coroutines/InlineTest;" , "", "()V", "logd", "", "logi", "test", "app"} )
public final class InlineTest {
   public final void test(a) {
      int var1 = 1;

      byte var2;
      for(var2 = 3; var1 <= var2; ++var1) {
         this.logd();
      }

      var1 = 1;

      for(var2 = 3; var1 <= var2; ++var1) {
         int $i$f$logi = false;
         Log.i("LOG".">>> logi"); }}private final void logd(a) {
      Log.d("LOG".">>> logd");
   }

   public final void logi(a) {
      int $i$f$logi = 0;
      Log.i("LOG".">>> logi"); }}Copy the code

As you can see from the decompilation, the logd() method is not inline. Instead, the logd() method is called one by one in a loop, resulting in a large number of in-and-out operations. The logi() method, modified with the inline keyword, copies a copy of the code into the body of the loop without calling the logi() method too often

(3) Easy transfer of generic objects and generic entity type acquisition (no strong transfer operation required)

Case 5

class InlineTest {
    fun test(a){
        val json = "{\"age\":18,\"name\":\"kk\"}"
        val generic = GenericTest().resolve<Generic>(json)
        Log.d("LogUtils"."age == ${generic.age}")
        Log.d("LogUtils"."name == ${generic.name}")}}/** * defines the inline function */
class GenericTest{
    inline fun <reified T> resolve(json: String): T {
        return resolve(T::class.java, json)
    }

    fun <T> resolve(clazz: Class<T>, json: String) : T{
        return Gson().fromJson(json, clazz) as T
    }
}

/** * defines an entity object */
data class Generic(
    val age: Int.val name: String
)
Copy the code

Running results:

D/LogUtils: age == 18
D/LogUtils: name == kk
Copy the code

By inlining the GenericTest object in Example 5, the GenericT gets the concrete entity object Generic, which does not need to be strong-cast, and the GenericTest object does not need to reference Generic

3. Kotlin’s generic invoke() function

The invoke() method, which is held by default in the Kotlin object class, can be overridden by the operator keyword

Case 6

enum class OperatorTest {

    TEST;

    operator fun invoke(data: String){
        Log.d("LogUtils"."data : $data")}}fun execute(a){
    val start = OperatorTest.TEST
    // The original invocation
    start.invoke(1 "test")
    // Simplify the invocation
    start("The test 2")}Copy the code

Running results:

D/LogUtils: dataTest:1
D/LogUtils: dataTest:2
Copy the code

The kotlin class contains the invoke() method by default and can be overloaded with the operator keyword to invoke the original way: class.invoke(···); Kotlin allows easy calls to: class()

reference

Lambda in Kotlin, this one is enough

2. Keywords and operators