Kotlin grammar manual

Posted by advancedfuture on Sat, 12 Feb 2022 07:26:33 +0100

Kotlin grammar manual (I)

When using kotlin, because the mastery is not reliable enough, it is still a habit of Java programming, which wastes the language features, convenience and indirectness provided by kotlin. When reading some Android open source libraries, it is difficult to read because many of them are written by kotlin syntax, and it is inconvenient to consult kotlin syntax, so I have to record kotlin syntax, It is convenient to review the past and consolidate their basic knowledge.

variable

In kotlin, variables are divided into variable variables (var) and immutable variables (val).

  • val: immutable reference, corresponding to the final variable in Java; Variables declared with val cannot be assigned again after initialization.
  • var: variable reference, corresponding to non final variables in Java; The value of a variable declared with var can be changed.

Immutable variables cannot change their state after assignment. Therefore, immutable variables can be said to be thread safe, because they cannot be changed. All threads access the same object, so there is no need to do access control. Developers should use immutable variables as much as possible, which can make the code closer to the functional programming style.

fun main() {
   
    //Declare an immutable variable of integer type. The value of intValue cannot be changed after initialization
    val intValue: Int = 100

    //Declare a variable of type double
    var doubleValue: Double = 100.0
}

When declaring variables, there is usually no need to explicitly specify the type of variables, which can be automatically deduced by the compiler according to the context. If a read-only variable has no initial default value when declared, the variable type must be specified, and it must be ensured that the variable can be initialized under each branch condition before use, otherwise the compiler will report an exception.

data type

Basic data type

In kotlin, everything is an object, and its member functions and attributes can be called in any variable, without distinguishing between the basic data type and its wrapper class. In other words, kotlin does not have the original basic types like those in Java, but types such as byte, char, integer, float or boolean , are still reserved, but all exist as objects.

    //In kotlin, int, long, float and other types still exist, but they exist as objects

    val intIndex: Int = 100
    //Equivalent to, the compiler automatically performs type derivation
    val intIndex = 100

    //The number type will not be automatically transformed, and explicit type conversion must be carried out
    val doubleIndex: Double = intIndex.toDouble()
    //The following code will prompt errors and require explicit type conversion
    //val doubleIndex: Double = intIndex

    val intValue: Int = 1
    val longValue: Long = 1
    //The following code will prompt an error because the data types of the two are inconsistent and need to be converted to the same type before comparison
    //println(intValue == longValue)

    //Char cannot be directly processed as a number and needs to be actively converted
    val ch: Char = 'c'
    val charValue: Int = ch.toInt()
    //The following code will prompt an error
    //val charValue: Int = ch

    //Octal is not supported
    //Binary
    val value1 = 0b00101
    //hexadecimal
    val value2 = 0x123

character string

String is represented by string type. Strings are immutable. Elements of string: access with index operator: s[i]; You can iterate over the string with a for loop, or you can connect the string with a +.

    val str = "hello"
    println(str[1])
    for (c in str) {
        println(c)
    }
    val str1 = str + " world"

kotlin supports the reference of local variables in the literal value of the string. You only need to add the character $before the variable name. In addition, it can also contain an expression enclosed in curly brackets {}. At this time, it will be evaluated automatically and the result will be merged into the string.

    val intValue = 100
    //You can include variables directly
    println("intValue value is $intValue") //intValue value is 100
    //You can also include expressions
    println("(intValue + 100) value is ${intValue + 100}")   //(intValue + 100) value is 200

If you need to represent literal ($) characters in the original string (it does not support backslash escape), you can use the following syntax:

    val price = "${'$'}100.99"
    println(price)  //$100.99

array

Arrays are represented in Kotlin by the {Array} class, which defines the} get} and} set} functions (which will be converted to []) and the} size} attribute according to the operator overloading convention, as well as some other useful member functions:

  1. arrayOf(): use the library function arrayOf() to create an array and pass the element value to it. For example, arrayOf(1, 2, 3) creates [1, 2, 3].
  2. arrayOfNulls: the library function arrayOfNulls() can be used to create an array of a specified size and initialize that all elements are empty.
  3. Array: array is a constructor that accepts the array size and a function parameter. The function used as the parameter can return the initial value of each element of a given index, as shown below:
// Create an array < string > with an array size of 5 and a function of (I * I) String array of tostring()
val asc = Array(5) { i -> (i * i).toString() }//["0", "1", "4", "9", "16"]
asc.forEach { println(it) }

Basic data type array

The type parameters of the Array type, such as the above Array, will always become the object type, so the Declaration "Array < int > will be an Array containing the boxing type (java.lang.Integer). If you want to create an Array of basic data types without boxing, you must use a special class of basic data type Array, such as IntArray, ByteArray, Boolean Array, etc, These classes have no inheritance relationship with {Array}, but they have the same method property set, and they also have corresponding factory methods.

val x: IntArray = intArrayOf(1, 2, 3)
x[0] = x[1] + x[2]
// Integer array with size 5 and value [0, 0, 0, 0, 0]
val arr = IntArray(5)

// For example: initialize the values in the array with constants
// Integer array with size 5 and value [42, 42, 42, 42, 42]
val arr = IntArray(5) { 42 }

// For example: use a lambda expression to initialize the values in the array
// Integer array with size 5 and value [0, 1, 2, 3, 4] (value initialized to its index value)
var arr = IntArray(5) { it * 1 }

aggregate

Sets in kotlin are divided into read-only sets and variable sets, as shown below:

Collection elementread-onlyvariable
ListlistOfmutableListOf,arrayListOf
SetsetOfmutableSetOf,hashSetOf,linkedSetOf,sortedSetOf
MapmapOfmutableMapOf,hashMapOf,linkedMapOf,sortedMapOf
  • A list is an ordered collection of elements that can be accessed by index. The element can appear multiple times in the list.
  • Set is a collection of unique elements, a set of objects without duplicates. Generally speaking, the order of elements in set is not important. For example, the alphabet is a set of letters.
  • A Map is a set of key value pairs. Keys are unique, and each key is mapped to exactly one value. Values can be repeated

Variability of read-only sets

Read only collections are not necessarily immutable. For example, suppose there is an object with a read-only type interface. The object has two different references, one is read-only and the other is variable. When the variable reference modifies the object, it is equivalent to "the read-only set has been modified" for the read-only reference. Therefore, the merging of read-only sets is not always thread safe. If you need to process data in a multithreaded environment, you need to ensure that the access to data is synchronized correctly, or use a data structure that supports concurrent access

For example, list1 and list1 refer to the same collection object, and list3's modification of the collection will affect list1 at the same time

    val list1: List<String> = JavaMain.names
    val list3: MutableList<String> = JavaMain.names
    list1.forEach { it -> println(it) } //leavesC Ye
    list3.forEach { it -> println(it) } //leavesC Ye
    for (index in list3.indices) {
        list3[index] = list3[index].toUpperCase()
    }
    list1.forEach { it -> println(it) } //LEAVESC YE

Set and nullability

Set nullability can be divided into three types:

  1. Can contain null collection elements
  2. The collection itself can be null
  3. The collection itself can be null and can contain null collection elements

For example, intList1 can contain null collection elements, but the collection itself cannot point to null; intList2 cannot contain null collection elements, but the collection itself can point to null; intList3 can contain null collection elements, and the collection itself can point to null

    //List<Int?>  Can you hold int? List of type values
    val intList1: List<Int?> = listOf(10, 20, 30, 40, null)
    //List<Int>?  Is a null able list
    var intList2: List<Int>? = listOf(10, 20, 30, 40)
    intList2 = null
    //List<Int?>?  Is a null able list and can hold int? Type value
    var intList3: List<Int?>? = listOf(10, 20, 30, 40, null)
    intList3 = null

Other data types

  • Any: any type is the supertype of all non empty types of kotlin, including basic data types such as Int. if the value of the basic data type is assigned to a variable of any type, it will be automatically boxed into Java Lang. integer.
  • Any?: Any? Type is the supertype of all nullable types of kotlin. If you want to make the variable store all possible values including null, you need to use any?
  • Unit: unit type is similar to void in Java. It can be used when the function has no return value. If the return value of the function is unit, the declaration can be omitted, and unit can also be used as a type parameter to declare a variable.
  • Nothing: nothing type has no value. It only makes sense when it is used as the return value of a function or as a type parameter of the return value of a generic function

function

The function in kotlin starts with the keyword fun, followed by the function name, followed by the parameter list wrapped in parentheses. If the function has a return value, add the return value type and separate it from the parameter list with a colon.

//fun is used to declare a function, double is the function name, x is the incoming parameter, int is the return value of the function, and the type is int
fun double(x: Int): Int {
    return 2 * x
}

Another is the expression function body, which is a function defined by a single line expression and an equal sign. The return value type of the expression function body can be omitted and the return value type can be inferred automatically. For example: fun double(x: Int) = x * 2

If the function has no meaningful return value, it can be declared as Unit or omitted. The following three methods are equivalent:

        fun test(str: String, int: Int): Unit {
            println(str.length + int)
        }

        fun test(str: String, int: Int) {
            println(str.length + int)
        }

        fun test(str: String, int: Int) = println(str.length + int)

Parameters of function

Named parameters

kotlin allows us to use named parameters, that is, when calling a function, the function parameter names can be marked together, so as to clearly express the meaning and function of the parameter. However, after specifying the name of a parameter, all subsequent parameters need to be marked with the name. As follows:

fun main() {
    //This method is wrong. After specifying the name of a parameter, all subsequent parameters need to be marked with the name
    compute(index = 110, "hello")
    
    compute(index = 120, value = "hello")
    compute(130, value = "hello")
}

fun compute(index: Int, value: String) {

}

Default parameters

Function parameters can have default values, which are used when corresponding parameters are omitted. This can reduce the number of overloads compared to other languages.

fun main() {
    compute(24)
    compute(24, "world")
}

fun compute(age: Int, name: String = "hello") {

}

When there are default parameters, only the parameters listed at the end can be omitted, and those in other positions cannot be omitted, as shown below:

fun main() {
    //Error, parameter name cannot be omitted
    // compute(24)
    // compute(24,100)
    
    //The parameter value can be omitted
    compute("hello", 24)
}

fun compute(name: String = "hello", age: Int, value: Int = 100) {}

However, if you use named parameters, you can omit any parameters with default values, and you can also pass in the required parameters in any order.

fun main() {
    compute(age = 24)
    compute(age = 24, name = "hello")
    compute(age = 24, value = 90, name = "hello")
    compute(value = 90, age = 24, name = "hello")
}

fun compute(name: String = "hello", age: Int, value: Int = 100) {

}

Variable parameter

The syntax of kotlin is different from that of Java. Variable parameters are declared by using the variable keyword instead

fun main() {
    compute()
    compute("hello")
    compute("hello", "world")
    compute("hello", "world", "kotlin")
}

fun compute(vararg name: String) {
    name.forEach { println(it) }
}

In Java, arrays can be directly passed to variable parameters, while kotlin requires explicit unpacking of arrays so that each array element can be called as a separate parameter in the function. This function is called expansion operator, which is used by adding one before the array parameter*

fun main() {
    val names = arrayOf("hello", "world", "kotlin")
    compute(* names)
}

fun compute(vararg name: String) {
    name.forEach { println(it) }
}

Local function

In Kotlin, nested functions are supported. Nested functions are called local functions

fun compute(name: String, country: String) {
    fun check(string: String) {
        if (string.isEmpty()) {
            throw IllegalArgumentException("Parameter error")
        }
    }
    check(name)
    check(country)
}

The check method body is placed in the compute method body. The check method is called a local method or a local function; Check can only be called in the compute method. Calling outside the compute method will cause compilation errors.

control flow

IF expression

In kotlin, if is an expression that returns a value. Therefore, there is no need for ternary operators (condition? Then: otherwise), because ordinary if can be competent for this role, so there is no ternary operator in kotlin.

// Traditional Use 
var max = a 
if (a < b) max = b

// With else 
var max: Int
if (a > b) {
    max = a
} else {
    max = b
}
 
// As an expression
val max = if (a > b) a else b

The branch of if can be a code block, and the last expression is the value of the block

//a. B is the last returned value of the block
val max = if (a > b) {
    print("Choose a")
    a
} else {
    print("Choose b")
    b
}

If you use , if , as an expression instead of a statement (for example, return its value or assign it to a variable), the expression needs to have an , else , branch

when expression

The when expression is similar to switch/case in Java, but it is more powerful. When can be used as either an expression or a statement. When compares the parameters with all branch conditions in order until a branch meets the conditions, and then it will run the expression on the right.

If when is used as an expression, the value of the qualified branch is the value of the whole expression. Different from Java switch/case, the parameter of when expression can be of any type, and the branch can also be a condition.

Like if, each branch of when expression can be a code block, and its value is the value of the last expression in the code block. If other branches do not meet the conditions, they will be evaluated in the else branch.

If when is used as an expression, there must be an else branch unless the compiler can detect that all possible conditions have been overridden. If many branches need to be handled in the same way, you can put multiple branch conditions together and separate them with commas.

    val value = 2
    when (value) {
        in 4..9 -> println("in 4..9") //Interval judgment
        3 -> println("value is 3")    //Equality judgment
        2, 6 -> println("value is 2 or 6")    //Multivalued equality judgment
        is Int -> println("is Int")   //Type judgment
        else -> println("else")       //If none of the above conditions are met, else is executed
    }

fun main() {
    //Return when expression
    fun parser(obj: Any): String =
            when (obj) {
                1 -> "value is 1"
                "4" -> "value is string 4"
                is Long -> "value type is long"
                else -> "unknown"
            }
}

The when statement can also be used without parameters

    when {
        1 > 5 -> println("1 > 5")
        3 > 1 -> println("3 > 1")
    }

For circulation

    //It is very similar to the way it is used in java
    val list = listOf(1, 4, 10, 34, 10)
    for (value in list) {
        println(value)
    }

Traversal by index

    val items = listOf("H", "e", "l", "l", "o")
    //Traverse the List by index
    for (index in items.indices) {
        println("${index}The corresponding values are: ${items[index]}")
    }

You can also get the current index and the corresponding value in each cycle

    val list = listOf(1, 4, 10, 34, 10)
    for ((index, value) in list.withIndex()) {
        println("index : $index , value :$value")
    }

You can also customize the cycle interval

    for (index in 2..10) {
        println(index)
    }

While loop and do/while loop

The use of both is similar to that in Java.

    val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
    var index = 0
    while (index < list.size) {
        println(list[index])
        index++
    }

    val list = listOf(1, 4, 15, 2, 4, 10, 0, 9)
    var index = 0
    do {
        println(list[index])
        index++
    } while (index < list.size)

Return and jump

Kotlin has three structural jump expressions (similar to those in java language):

  • return returns by default from the function that most directly surrounds it or from an anonymous function
  • break terminates the loop that most directly surrounds it
  • continue continues the next cycle that most directly surrounds it
    In kotlin, any expression can be marked with a label. The format of the label is an identifier followed by the @ symbol. For example, abc @ and fooBar @ are valid labels
fun main() {
    fun1()
}

fun fun1() {
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    loop@ for (it in list) {
        if (it == 8) {
            continue
        }
        //When the value is 23, exit the loop loop of the tag
        if (it == 23) {
            break@loop
        }
        println("value is $it")
    }
    println("function end")
}

In general, it is more convenient to use an implicit label. The implicit label has the same name as the function that accepts the lambda. return can also add label restrictions (as follows:)

fun fun3() {
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach {
        if (it == 8) {
            //This is the restriction of adding the implicit label forEach to the return, so that it will only terminate the return in the current cycle of forEach. The effect is the same as that of continue
            return@forEach
        }
        println("value is $it")
    }
    println("function end")
 //Running fun3 method will output the following results:
//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun4() {
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach loop@{
        if (it == 8) {
            return@loop//Like fun3, this is an explicit tag with loop added
        }
        println("value is $it")
    }
    println("function end")
//Running fun4 method will output the following results:
//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun5() {
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
        if (value == 3) {
            //The part returns to the caller of the anonymous function, that is, the forEach loop
            return
        }
        println("value is $value")
    })
    println("function end")
}
//When running fun5 method, the following results will be output:
//value is 1
//value is 2
//value is 4
//value is 5
//function end

section

Kotlin can be accessed by calling {kotlin The [rangeTo()] function and its operators in the ranges package Form easily creates an interval of two values. Usually, rangeTo() will be supplemented by , in , or! In function

if (i in 1..4) {  // Equivalent to I > = 1 & & I < = 4
    print(i)
}

if (i in 1.rangeTo(4)) {  // Same as above
    print(i)
}

When ranges of numeric type are iterated, it is equivalent to the effect of the loop of fori with index in java

for (i in 1..4) {
    print(i)
}

To iterate numbers in reverse, use the [downTo] function instead of Or rangeTo().

for (i in 4 downTo 1) print(i)

This is the number of iterations with any step size (not necessarily 1) through the [step] function

for (i in 1..8 step 2) print(i)
println()
for (i in 8 downTo 1 step 2) print(i)

The above declarations are all closed intervals. If you want to declare an open interval), you can use the until function

for (i in 1 until 4) {
    println(i)
}

Air safety

In kotlin, the type system divides a reference into two types that can accommodate null (nullable reference) or cannot accommodate null (non empty reference). Regular variables cannot point to null. If you want a variable to store null references, you need to explicitly add a question mark after the type name?.

    //The name variable cannot be assigned null
    var name: String = "hello"
    
    //The name variable can be assigned null
    var name: String? = "hello"

kotlin's explicit support for nullable types helps to prevent exception problems caused by NullPointerException. The compiler does not allow directly calling the properties of nullable variables without null checking.

var name: String? = "hello"

val l = name.length // The compiler will report an error because the name variable may be null

When writing code, we can judge the null of nullable variables, and kotlin also provides us with safe calling operators

Secure call

?.: If the variable value is not empty, the method or property of the variable will be called, otherwise null will be returned directly.

//B variable declaration is of nullable type, when B= When null, the value of b.length is returned
//When b=null, B Length will automatically return null. You can see that the type returned by this expression is Int?
println(b?.length)

Security calls are also useful in chain calls. For example, if an employee Bob may (or may not) be assigned to a department and another employee may be the head of the Department, get the name of the head of Bob's Department (if any). We write: Bob department?. head?. name

If any attribute (link) is empty, the chain call will return null.

If you want to perform an operation only on non null values, the safe call operator can be used with [let]:

val listWithNulls: List<String?> = listOf("Kotlin", null)
for (item in listWithNulls) {
    item?.let { println(it) } // Output Kotlin and ignore null
}

Safe calls can also appear to the left of the assignment. In this way, if any receiver in the call chain is empty, the assignment will be skipped, and the expression on the right will not be evaluated at all:

// If 'person' or 'person Department ` if one of them is empty, this function will not be called:
person?.department?.head = managersPool.getManager()

Elvis operator

When we have an nullable reference , b , if , b , is not empty, I use it; If b is empty, we want to use a non empty value, as shown below:

val l: Int = if (b != null) b.length else -1

In addition to the if expression, it can also be expressed through the Elvis operator. Write?:

//When expression B When length is empty (i.e. b=null), it is assigned as - 1
val l = b?.length ?: -1

If?: If the expression on the left is not empty, the elvis operator returns the expression on the left, otherwise it returns the expression on the right. Note that the expression on the right side is evaluated if and only if the left side is empty.

Safe type conversion

If the object is not the target type, regular type conversion may result in a ClassCastException. Another option is to use safe type conversion as?, null if the conversion attempt is unsuccessful:

//If a is not of Int type, null will be returned
val aInt: Int? = a as? Int

Non null assertion operator!!

!! Is to convert any value to a non empty type. If the value is empty, an exception will be thrown

//If b=null, a null pointer NPE exception will be thrown
val l = b!!.length

Nullable type extension

Defining extension functions for nullable types is a more powerful way to handle null values, allowing the receiver to call null and handle null in the function, rather than calling its methods after ensuring that the variable is not null

    //It can be called normally without null pointer exception
    val name: String? = null
    println(name.isNullOrEmpty()) //true

Type detection and type conversion

Type check is{and! Is operator

At run time, by using the {is} operator or its negative form! Is to detect whether the object conforms to the given type

if (obj is String) {
    print(obj.length)
}

if (obj !is String) { // And! (obj is String) same
    print("Not a String")
}
else {
    print(obj.length)
}

Intelligent conversion

In many cases, explicit conversion operators do not need to be used in Kotlin, because the compiler tracks the {is- detection of immutable values and [explicit conversion], and automatically inserts (SAFE) conversion when needed:

fun demo(x: Any) {
    if (x is String) {
        print(x.length) // x is automatically converted to a string
    }
}

if (x !is String) return
print(x.length) // x is automatically converted to a string

// `||`The x on the right is automatically converted to a string
if (x !is String || x.length == 0) return

// `&&`The x on the right is automatically converted to a string
if (x is String && x.length > 0) {
    print(x.length) // x is automatically converted to a string
}

//The same is true for when expressions and while loops
when (x) {
    is Int -> print(x + 1)
    is String -> print(x.length + 1)
    is IntArray -> print(x.sum())
}

Unsafe conversion operator

If conversion is not possible, the conversion operator will throw an exception, so it is not safe. Unsafe conversion in Kotlin is completed by the operator as; Nullable types and non nullable types are different types and cannot be converted, such as String and String? Are different types.

Safe conversion operator

To avoid throwing exceptions, you can use the safe conversion operator as, It can return null on failure:

//as? On the right are non empty types, and the declaration is nullable. If you use as, you will throw an exception. Here, use as? null can be returned, so the result is nullable
val x: String? = y as? String

Scope function

The kotlin standard library contains several functions whose sole purpose is to execute code blocks in the context of objects. When such a function is called on an object and a lambda expression is provided, it forms a temporary scope. In this scope, the object can be accessed without its name. These functions are called scope functions

These functions basically do the same thing: execute a block of code on an object. The difference is how the object is used in the block and what the result of the entire expression is.

The following is a typical usage of a scope function:

Person("Alice", 20, "Amsterdam").let {
    println(it)
    it.moveTo("London")
    it.incrementAge()
    println(it)
}

If you don't use let to write this code, you must introduce a new variable and repeat its name every time you use it.

val alice = Person("Alice", 20, "Amsterdam")
println(alice)
alice.moveTo("London")
alice.incrementAge()
println(alice)

Scope functions don't introduce any new technology, but they can make your code more concise and readable.

difference

Several scope functions are provided in the standard library. They are very similar in essence. There are two main differences between each scope function:

  • How context objects are referenced
  • Return value

Context object: this or it

this

The keyword this can be seen as receiving a lambda expression. run, with , and , apply , refer to the context object through the keyword , this , and the context object can be accessed in their lambda expressions as in ordinary class functions. In most scenarios, when you access the recipient object, you can omit {this to make your code shorter.

val adam = Person("Adam").apply { 
    age = 20                       // And this Age = 20 or Adam Age = 20
    city = "London"
}
println(adam)
it

The IT keyword can be seen as an argument to a lambda expression. For example, let # and # also # take the context object as the parameter of lambda expression. If the parameter name is not specified, the object can be accessed with the implicit default name # it #

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()

In addition, when passing a context object as a parameter, you can specify a custom name within the scope for the context object.

fun getRandomInt(): Int {
    //Custom name value
    return Random.nextInt(100).also { value ->
        writeToLog("getRandomInt() generated value $value")
    }
}

val i = getRandomInt()

Return value

According to the returned results, scope functions can be divided into the following two categories:

  • apply , and , also , return context objects.
  • let, run , and , with , return the result of lambda expression
Return context object

The return values of apply , and , also , are the context object itself.

val numberList = mutableListOf<Double>()

//The returned object can be called in a chain
numberList.also { println("Populating the list") }
    .apply {
        add(2.71)
        add(3.14)
        add(1.0)
    }
    .also { println("Sorting the list") }
    .sort()

Of course, it can also be used in the return statement of the function that returns the context object

fun getRandomInt(): Int {
    return Random.nextInt(100).also {
        writeToLog("getRandomInt() generated value $it")
    }
}

val i = getRandomInt()
Returns the result of the expression

let, run, and with return the result of a lambda expression

val numbers = mutableListOf("one", "two", "three")
val countEndsWithE = numbers.run { 
    add("four")
    add("five")
    count { it.endsWith("e") }//Returned results
}
//There are 3 elements that end with e.
println("There are $countEndsWithE elements that end with e.")

In addition, you can ignore the return value and only use the scope function to create a temporary scope for the variable.

val numbers = mutableListOf("one", "two", "three")
with(numbers) {
    val firstItem = first()
    val lastItem = last()        
    println("First item: $firstItem, last item: $lastItem")
}

Function selection

Several functions have been mentioned earlier, so how to choose the appropriate scope function?, The following is a list of the main differences between them

functionobject referenceReturn valueIs it an extension function
letitLambda expression resultyes
runthisLambda expression resultyes
run-Lambda expression resultNo: call context free object
withthisLambda expression resultNo: take the context object as a parameter
applythisContext objectyes
alsoitContext objectyes

The following is a brief summary of scope functions:

  • Execute a lambda expression on a non null object: let
  • Introducing an expression as a variable into a local scope: let
fun main() {
    val nickName = "leavesC"
    val also = nickName.let {
        it.length
    }
    println(also) //7
}
  • Object configuration: apply
val adam = Person("Adam").apply {
    age = 32
    city = "London"        
}
println(adam)
  • Object configuration and calculation result: run
val service = MultiportService("https://example.kotlinlang.org", 80)

val result = service.run {
    port = 8080
    query(prepareRequest() + " to port $port")
}
  • Run the statement where an expression is needed: non extended run
val hexNumberRegex = run {
    val digits = "0-9"
    val hexDigits = "A-Fa-f"
    val sign = "+-"

    Regex("[$sign]?[$digits$hexDigits]+")
}

for (match in hexNumberRegex.findAll("+1234 -FFFF not-a-number")) {
    println(match.value)
}
  • Additional effect: also
val numbers = mutableListOf("one", "two", "three")
numbers
    .also { println("The list elements before adding new one: $it") }
    .add("four")
  • A set of function calls for an object: with
val result = with(StringBuilder()) {
        append("leavesC")
        append("\n")
        for (letter in 'A'..'Z') {
            append(letter)
        }
        toString()
    }
    println(result)

takeIf and takeUnless

In addition to the scope functions, the standard library also contains the functions , takeIf , and , takeUnless. These two functions allow you to embed object state checks into the call chain.
takeIf receives a function whose return value type is bool. When the return value of this parameter is true, it returns the recipient object itself, otherwise it returns null.
The judgment condition of takeUnless is opposite to takeIf.

fun main() {
    println(check("leavesC")) //7
    println(check(null)) //0
}

fun check(name: String?): Int {
    return name.takeIf { !it.isNullOrBlank() }?.length ?: 0
}

Topics: Java Android Design Pattern kotlin