Functions in kotlin

Time:2020-6-4

This article starts with WeChat official account of vivo Internet technology.
Link: https://mp.weixin.qq.com/s/UV23Uw_ 969oVhiOdo4ZKAw
Author: Lian lingneng

Kotlin, which has been officially announced by android as the existence of kotlin first, went to the official Android documentation and found that the provided sample code has become kotlin. Kotlin’s pragmatic style provides many features to help developers reduce redundant code writing, which can improve efficiency and reduce exceptions.

This paper briefly discusses the functions in kotlin, including expression function body, named parameter, default parameter, top-level function, extended function, local function, lambda expression, member reference, with / apply function, etc. Starting from examples, from the general writing method to the use of features to simplify, and then to the principle analysis.

1. Expression function body

Let’s take a look at the related concepts of function declaration through the following simple example. The keyword of function declaration is fun, well, simpler than JS function.

In kotlin, the parameter type is put in the variable: after that, the function return type is also.

fun max(a: Int, b: Int) : Int {
    if (a > b) {
        return a 
    } else {
        return b
    }
}

Of course, kotlin has the function of type derivation. If you can deduce the type according to the function expression, you can also not write the return type.

However, the above is a little tedious and simple. In kotlin, if is an expression, that is, it has a return value. Therefore, you can return directly. In addition, you can omit braces for only one line and one sentence in the judgment formula:

fun max(a: Int, b: Int)  {
    return if (a > b) a else b
}

Can it be simpler? Yes, if is an expression, then it can be returned through the expression function body:

fun max(a: Int, b: Int)  = if(a > b)  a else b

In the end, only one line of code is needed.

Example

Let’s look at the following example, which will be modified later. This function outputs the collection in a format other than the default toString ().

<T> It’s a generic type, where all the elements in the parameter set are of type T. Returns the string type. fun <T> joinToString(

        collection: Collection<T>,
        separator: String,
        prefix: String,
        postfix: String
): String {
    val sb = StringBuilder(prefix)
    for ((index, element) in collection.withIndex()) {
        if (index > 0) sb.append(separator)
        sb.append(element)
    }

    sb.append(postfix)
    return sb.toString()
}

2. Named parameter call

First, let’s look at the following function calls. Compared with Java, kotlin can be called similar to JavaScript with named parameters, and it can be called in the order of function declaration instead of in the order of confusion, such as the following:

joinToString(separator = " ", collection = list, postfix = "}", prefix = "{")

// example
val list = arrayListOf("10", "11", "1001")
println(joinToString(separator = " ", collection = list, postfix = "}", prefix = "{"))

>>> {10 11 1001}

3. Default parameters

In Java, there is the saying of overloading, or in JavaScript, there is a default parameter value. Kotlin adopts the default parameter value. When called, it is not necessary to pass arguments to parameters with default parameter values. The above function is changed as follows:

fun <T> joinToString(
        collection: Collection<T>,
        separator: String = " ",
        prefix: String = "[",
        postfix: String = "]"
): String {
    ...
}

// 
joinToString(list)

When calling, if the default parameter value meets the requirements, you can just pass in the collection list.

4. Top level function

Unlike in Java, functions can only be defined in each class, kotlin adopts the method of JavaScript and can define functions anywhere in the file. This function is called the top-level function.

After compilation, the top-level function will become a static function under the file class, for example, the file name is join.kt The jointostring function defined below can be JoinKt.joinToSting Call, where joinkt is the compiled class name.

//Compile to static function
//File name join.kt
package strings
fun joinToString() : String {...}

/* Java */
import strings.JoinKt;
JoinKt.joinToSting(....)

Take a look at the compiled effect of the above functions: / / decompile the result after compiling it into a class file

@NotNull
public static final String joinToString(@NotNull Collection collection, @NotNull String separator, @NotNull String prefix, @NotNull String postfix) {
      Intrinsics.checkParameterIsNotNull(collection, "collection");
      Intrinsics.checkParameterIsNotNull(separator, "separator");
      Intrinsics.checkParameterIsNotNull(prefix, "prefix");
      Intrinsics.checkParameterIsNotNull(postfix, "postfix");
      StringBuilder sb = new StringBuilder(prefix);
      int index = 0;

      for(Iterator var7 = ((Iterable)collection).iterator(); var7.hasNext(); ++index) {
         Object element = var7.next();
         if (index > 0) {
            sb.append(separator);
         }

         sb.append(element);
      }

      sb.append(postfix);
      String var10000 = sb.toString();
      Intrinsics.checkExpressionValueIsNotNull(var10000, "sb.toString()");
      return var10000;
   }

//Default function value
public static String joinToString$default(Collection var0, String var1, String var2, String var3, int var4, Object var5) {
      if ((var4 & 2) != 0) {
         var1 = " ";
      }

      if ((var4 & 4) != 0) {
         var2 = "[";
      }

      if ((var4 & 8) != 0) {
         var3 = "]";
      }

      return joinToString(var0, var1, var2, var3);

Next let’s look at an important feature of kotlin, the extension function.

5. Extension function

  • An extension function is a member function of a class, but it is defined outside the class
  • Extension functions cannot access private or protected members
  • Extended functions are also compiled into static functions

Therefore, it can be encapsulated by extension functions on the basis of Java libraries, pretending to call kotlin’s own libraries. This is what collection does in kotlin.

Then we will make a modification to the jointostring above. The final version:

fun <T> Collection<T>.joinToString(
        separator: String = " ",
        prefix: String = "[",
        postfix: String = "]"
): String {
    val sb = StringBuilder(prefix)
    for ((index, element) in this.withIndex()) {
        if (index > 0) sb.append(separator)
        sb.append(element)
    }

    sb.append(postfix)
    return sb.toString()
}

This function is declared as an extension function of the collection interface class, so that it can be called directly through the list, and this can be used in the extension function as usual. Here this refers to the receiver object, and here it is the list.

val list = arrayListOf("10", "11", "1001")
println(list.joinToString())

>>> [10 11 1001]

Often, we need to refactor the code. One of the important measures is to reduce duplicate code. In Java, we can extract independent functions, but sometimes it is not good for the overall structure. Kotlin provides local functions to solve this problem.

6. Local function

As the name implies, a local function is a function that can be defined within a function. First, let’s look at an example where local functions are not used. In this example, the user name and address passed in are verified first. Only when they are not empty can they be saved in the database:

class User(val id: Int, val name: String, val address: String)

fun saveUser(user: User) {
    if (user.name.isEmpty()) {
        throw IllegalArgumentException(
            "Can't save user ${user.id}: empty Name")
    }

    if (user.address.isEmpty()) {
        throw IllegalArgumentException(
            "Can't save user ${user.id}: empty Address")
    }

    // Save user to the database
}

There are duplicate codes above, that is, the verification of name and address is repeated, but the input parameters are different. Therefore, a verification function can be extracted and rewritten with a local function:

fun saveUser(user: User) {
    fun validate(value: String, fieldName: String) {
        if (value.isEmpty()) {
            throw IllegalArgumentException(
                    "Can't save user ${user.id}: empty $fieldName")
        }
    }

    validate(user.name, "Name")
    validate(user.address, "Address")
}

Layout functions have access to all parameters and variables in their functions.

If you don’t support lambda, you’re too embarrassed to call yourself a modern language. Come and see the performance in kotlin.

7. Lambda expression

Lambda is essentially a small piece of code that can be passed to other functions, passing around as values

Lambda expression starts with the left brace and ends with the right brace. Arrow – > is divided into two sides. The left side is the input parameter and the right side is the function body.

val sum = {x : Int, y : Int -> x + y}
println(sum(1, 2))

//Can run directly
run { println(42)}

If the lambda expression is the last argument of the function call, it can be placed outside the bracket;

When lambda is the only argument of a function, empty parentheses in the calling code can be removed;

As with local variables, if the type of lambda parameter can be derived, there is no need to display the specified.

val people = listOf(User(1, "A", "B"), User(2, "C", "D"))
people.maxBy { it.id }

If you use lambda inside a function, you can access the parameters of the function, as well as the local variables defined before lambda.

fun printProblemCounts(responses: Collection<String>) {
    var clientErrors = 0
    var serverErrors = 0
    responses.forEach {
        if (it.startsWith("4")) {
            clientErrors++
        } else if (it.startsWith("5")) {
            serverErrors++
        }
    }
    println("$clientErrors client errors, $serverErrors server errors")
}

Considering such a case, if a function A receives a function type parameter, but this parameter function has been defined elsewhere as function B, there is a way to introduce a Lambda expression to A, calling B in this expression, but this is a bit cumbersome. Is there any way to get B directly?

I’ve said so much. I must have… That’s the member reference.

8. Member reference

If the lambda happens to be a delegate of a function or property, you can replace it with a member reference.

people.maxBy(User::id)

PS: do not put parentheses after the name of a member reference, whether it is a function or a property

Reference top level function

fun salute() = println("Salute!")
run(::salute)

If lambda wants to delegate to a function receiving multiple parameters, it is very convenient to provide member reference instead: fun sendemail (person: person, message: String){

println("message: $message")
}

val action = { person: Person, message: String ->
        sendEmail(person, message)
}
//Action can be simplified as follows
val action = ::sendEmail
// 
action(p, "HaHa")

You can use the constructor to reference, store or delay the action of creating a class instance. The reference form of the constructor is to specify the class name after the double colon:

data class Person(val name: String, val age: Int)
val createPerson = ::Person
val p = createPerson("Alice", 29)

Extension functions can also be referenced in the same way.

fun Person.isAdult() = age>= 21
val predicate = Person::isAdult

If you don’t look at the lower level, it’s not professional enough, and it’s not strong enough. Next, I’ll explore the principle of lambda.

9. Lambda expression principle

Since kotlin 1.0, every lambda expression will be compiled into an anonymous class unless it is an inline lambda. Later versions plan to support Java 8 bytecode generation. Once implemented, the compiler can avoid generating an independent. Class file for each lambda expression.

If lambda captures variables, each captured variable will have corresponding fields in the anonymous class, and each call will create a new instance of the anonymous class. Otherwise, a single instance will be created. The name of the class is derived from the function name of the lambda declaration plus the suffix. In this case, testlambdakt $main $1. Class.

// TestLambda.kt
package ch05

fun salute(callback: () -> Unit) = callback()

fun main(args: Array<String>) {
    salute { println(3) }
}

After compilation, two files are generated.

Mode                LastWriteTime         Length Name
----                -------------         ------ ----
-a----        2019/7/24     14:33           1239 TestLambdaKt$main$1.class
-a----        2019/7/24     14:35           1237 TestLambdaKt.class

First look at testlambdakt $main $1. Class, construct a static instance CH05. Testlambdakt $main $1 instance, assign a value when the class is loaded, inherit the interface function0, and implement the invoke method:

final class ch05.TestLambdaKt$main$1 extends kotlin.jvm.internal.Lambda implements kotlin.jvm.functions.Function0<kotlin.Unit>
  minor version: 0
  major version: 50
  flags: ACC_FINAL, ACC_SUPER
  Constant pool:...
{
  public static final ch05.TestLambdaKt$main$1 INSTANCE;
    descriptor: Lch05/TestLambdaKt$main$1;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  public java.lang.Object invoke();
    descriptor: ()Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_BRIDGE, ACC_SYNTHETIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokevirtual #12                 // Method invoke:()V
         4: getstatic     #18                 // Field kotlin/Unit.INSTANCE:Lkotlin/Unit;
         7: areturn

  public final void invoke();
    descriptor: ()V
    flags: ACC_PUBLIC, ACC_FINAL
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_3
         1: istore_1
         2: getstatic     #24                 // Field java/lang/System.out:Ljava/io/PrintStream;
         5: iload_1
         6: invokevirtual #30                 // Method java/io/PrintStream.println:(I)V
         9: return
      LineNumberTable:
        line 6: 0
        line 6: 9
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  this   Lch05/TestLambdaKt$main$1;

  ch05.TestLambdaKt$main$1();
    descriptor: ()V
    flags:
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: iconst_0
         2: invokespecial #35                 // Method kotlin/jvm/internal/Lambda."<init>":(I)V
         5: return

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #2                  // class ch05/TestLambdaKt$main$1
         3: dup
         4: invokespecial #56                 // Method "<init>":()V
         7: putstatic     #58                 // Field INSTANCE:Lch05/TestLambdaKt$main$1;
        10: return
}

Let’s look at another class TestLambdaKt.class In the main method, TestLambdaKt$main$1.INSTANCE is passed to the method salute, and the interface method invoke is called in the method salute, see above.

public final class ch05.TestLambdaKt
  minor version: 0
  major version: 50
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
  ...
{
  public static final void salute(kotlin.jvm.functions.Function0<kotlin.Unit>);
    descriptor: (Lkotlin/jvm/functions/Function0;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: ldc           #10                 // String callback
         3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
         6: aload_0
         7: invokeinterface #22,  1           // InterfaceMethod kotlin/jvm/functions/Function0.invoke:()Ljava/lang/Object;
        12: pop
        13: return
      LineNumberTable:
        line 3: 6
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      14     0 callback   Lkotlin/jvm/functions/Function0;
    Signature: #7                           // (Lkotlin/jvm/functions/Function0<Lkotlin/Unit;>;)V
    RuntimeInvisibleParameterAnnotations:
      0:
        0: #8()

  public static final void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    Code:
      stack=2, locals=1, args_size=1
         0: aload_0
         1: ldc           #27                 // String args
         3: invokestatic  #16                 // Method kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
         6: getstatic     #33                 // Field ch05/TestLambdaKt$main$1.INSTANCE:Lch05/TestLambdaKt$main$1;
         9: checkcast     #18                 // class kotlin/jvm/functions/Function0
        12: invokestatic  #35                 // Method salute:(Lkotlin/jvm/functions/Function0;)V
        15: return
      LineNumberTable:
        line 6: 6
        line 7: 15
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      16     0  args   [Ljava/lang/String;
    RuntimeInvisibleParameterAnnotations:
      0:
        0: #8()
}

PS: there is no such thing as anonymous object in lambda: there is no way to refer to the anonymous class instance converted by lambda. From the compiler’s point of view, lambda is a code block, not an object, and it cannot be regarded as an object reference. This reference in lambda points to the class surrounding it.

What if you want to use this in the normal sense in lambda? This requires a function with a receiver. Take a look at the two more commonly used functions with and apply.

10. With function

Directly go to the source code of kotlin, with is declared as an inline function here (I’ll find an opportunity to say later), receives two parameters, and calls the lambda expression on the receiver in the function body. In lambda expression, you can refer to the receiver object through this.

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()

Here’s an example:

fun alphabet(): String {
    val result = StringBuilder()
    for (letter in 'A'..'Z') {
         result.append(letter)
    }
    result.append("\nNow I know the alphabet!")
    return result.toString()
}

With transformation, there is no need to display the append call through StringBuilder in with.

fun alphabet(): String {
    val result = StringBuilder()
    return with(result) {
        for (letter in  'A'..'Z') {
            append(letter)
        }
        append("\nNow I know the alphabet!")
        this.toString()
    }
}

//Go further
fun alphabet() = with(StringBuilder()) {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I know the alphabet!")
    toString()
}

The value returned by with is the result of executing the lambda code, which is the value of the last expression in the lambda. If you want to return the receiver object instead of the lambda result, you need to use the apply function.

11. Apply function

The apply function is as like as two peas with functions. The only difference is that apply always returns objects that are passed to it as a real argument, that is, the recipient object.

/**
 * Calls the specified function [block] with `this` value as its receiver and returns `this` value.
 */
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }

Apply is declared as an extension function whose receiver becomes the receiver of the lambda passed in as an argument.

fun alphabet() = StringBuilder().apply {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I know the alphabet!")
}.toString()

You can call library functions to simplify:

fun alphabet() = buildString {
    for (letter in 'A'..'Z') {
        append(letter)
    }
    append("\nNow I know the alphabet!")
}

//
/**
 * Builds new string by populating newly created [StringBuilder] using provided [builderAction]
 * and then converting it to [String].
 */
@kotlin.internal.InlineOnly
public inline fun buildString(builderAction: StringBuilder.() -> Unit): String =
        StringBuilder().apply(builderAction).toString()

12. Summary

This paper only talks about some features of functions in kotlin, but not all of them, such as inline functions, higher-order functions, etc. because it is too long to write down, we will add later. From the above examples, I can also roughly feel kotlin’s pragmatic style, which provides many features to help developers reduce redundant code writing, improve efficiency, reduce exceptions, make program apes leave work early, and keep their hair dark and beautiful forever.

More contentVivo Internet technologyWeChat official account

Functions in kotlin

Note: for reprint, please contact wechat:labs2020Contact.