Basics of Android Development - Introduction to Kotlin

What is Kotlin

Java code needs to be compiled to generate a special class file before running, and then the Java virtual machine recognizes and interprets these class files, and Kotlin, as a new programming language, compiles its code into such a class file, and also That is to say, although the two seem to be two different programming languages, they are actually the same in essence.

How to run Kotlin code

There are roughly three ways to run Kotlin code:

  • Use the IDEA tool to create a Kotlin project, and then you can run the Kotlin code
  • Run Kotlin code online
  • Using Android Studio, although Android Studio creates an Android project, you can still run Kotlin code independently as long as you write the main function of Kotlin

Here we use Android Studio to run Kotlin code.

For example, create a Kotlin file under the same-level package structure of MainActivity, and then write HelloWorld:

package com.example.helloworld

fun main() {
    println("Hello world!")
}

The output in the run of Android Studio is:

Hello world!

However, an error was reported in the middle of compilation here:

Manifest merger failed with multiple errors, see logs

This is because the sdk and Kotlin compiler versions do not match, resolve the version:

  • Download the lower version sdk in file-setting-system setting-android sdk, such as Android 11 (api 30)
  • Then in build.gradle, modify the parameters of compileSdk and targetSdk to 30

variables and functions

variable

When defining a variable in Kotlin, only two keywords are allowed before the variable:

  • var: value, used to declare an immutable variable, which cannot be reassigned after the initial assignment, corresponding to the final variable in Java
  • var: variable, used to declare a variable variable, this variable can still be reassigned after the initial assignment, corresponding to the non-final variable in Java

For example, code like this is reasonable:

package com.example.helloworld

fun main() {
    val a = 10
    var b = 20
    b = 30
    println("a =" + a + ",b = " + b)
}

This is because Kotlin uses a type deduction mechanism, but if you want to assign a value to a variable, you need to explicitly declare the variable type:

package com.example.helloworld

fun main() {
    val a = 10
    var b: Int
    b = 30
    println("a =" + a + ",b = " + b)
}

At this time, the variable b is limited to only be of type Int, and values ​​of other types cannot be used for assignment.

At the same time, it can be seen from the above that Kotlin's variable type Int is capitalized, which is different from Java, which means that the object data type used by variables in Kotlin, that is, Int is a class, and other data types are similar, and also need capital.

function

The syntax rules for functions in Kotlin are:

fun func(param1:Int, param2:Int):Int {
    return 0
}

Its form is:

  • fun: keyword for defining functions
  • func: the function name
  • param1:Int: It is the parameter declaration format, the front is the parameter name, and the back is the parameter type
  • Int: The last Int is the return value type, this part is optional

For example, the following code simply illustrates the use of the function:

package com.example.helloworld

import kotlin.math.max

fun Larger(num1:Int, num2:Int):Int {
    return max(num1, num2)
}

fun main() {
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))
}

At the same time, the above form can also use the syntactic sugar form in Kotlin. When there is only one line of code in a function, Kotlin can write the only line of code at the end of the function definition instead of writing the function body. For example, the above code can be transformed into:

package com.example.helloworld

import kotlin.math.max

//fun Larger(num1:Int, num2:Int):Int = max(num1, num2)
fun Larger(num1:Int, num2:Int) = max(num1, num2)

fun main() {
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))
}

logic control

if conditional statement

There are two main implementation forms of conditional statements in Kotlin: if statement and when statement.

First look at the if conditional statement:

package com.example.helloworld

import kotlin.math.max

fun Larger(num1:Int, num2:Int):Int {
    if (num1 > num2) {
        return num1
    } else {
        return num2
    }
}

fun main() {
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))
}

The if statement in Kotlin has an additional function, that is, the if statement can have a return value, and the return value is the return value of the last line of code in each condition of the if statement. Therefore, the above code can be written as:

package com.example.helloworld

import kotlin.math.max

fun Larger(num1:Int, num2:Int):Int {
    return if (num1 > num2) {
        num1
    } else {
        num2
    }
}

fun main() {
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))
}

That is, you can use the if statement to provide a return value for return, or to assign a value to a variable.

In the form of syntactic sugar mentioned earlier, the code can be changed to:

package com.example.helloworld

import kotlin.math.max

fun Larger(num1:Int, num2:Int):Int = if (num1 > num2) num1 else num2

fun main() {
    val a = 10
    var b: Int
    b = 30
    println("Larger =" + Larger(a,b))
}

when conditional statement

The when conditional statement is a bit like the switch statement in Java.

package com.example.helloworld

import kotlin.math.max

fun getScore(name:String) = if (name == "Tom") {
    86
} else if (name == "Jim") {
    77
} else if(name == "Jack") {
    95
} else {
    0
}

fun main() {
    println("Score =" + getScore("Tom"))
}

The above code using the when conditional statement is:

package com.example.helloworld

import kotlin.math.max

fun getScore(name:String) = when (name) {
    "Tom" -> 86
    "Jim" -> 77
    "Jack" -> 95
    else -> 0
}

fun main() {
    println("Score =" + getScore("Tom"))
}

And when the execution logic has only one line of code, {} can be omitted.

At the same time, the when statement also allows type matching:

package com.example.helloworld

import kotlin.math.max

fun checkNumber(num:Number) {
    when (num){
        is Int -> println("num is Int")
        is Double -> println("num is Double")
        else -> println("no support")
    }
}

fun main() {
    checkNumber(10.1)
}

You can also use when without parameters:

package com.example.helloworld

import kotlin.math.max

fun getScore(name:String) = when  {
    name == "Tom" -> 86
    name == "Jim" -> 77
    name == "Jack" -> 95
    else -> 0
}

fun main() {
    println("Score =" + getScore("Tom"))
}

loop statement

There are also two forms of loop statements in Kotlin, while loop and for loop.

However, the while loop in Kotlin is similar to the while loop in Java, and only the for loop is explained here.

package com.example.helloworld

fun main() {
    for (i in 1..10) {
        println(i)
    }
}

In the above for loop, 1..10 is in the form of an interval, that is, [1, 10]. It can be seen that the for loop here is still different from the for loop in Java.

But if the interval [1, 10) is needed, it is in the following form:

package com.example.helloworld

fun main() {
    for (i in 1 until 10) {
        println(i)
    }
}

And if you want to modify the step size, it is the following form:

package com.example.helloworld

fun main() {
    for (i in 1 until 10 step 2) {
        println(i)
    }
}

And if you need a descending closed interval, it is the following form:

package com.example.helloworld

fun main() {
    for (i in 10 downTo 1) {
        println(i)
    }
}

Object-Oriented Programming

classes and objects

package com.example.helloworld

class Person {
    var name = ""
    var age = 0

    fun printInfo() {
        println("name is " + name + ",age is " + age)
    }
}

fun main() {
    var tmp = Person()
    tmp.name = "Tom"
    tmp.age = 15

    tmp.printInfo()
}

From the above point of view, the definition and instantiation of classes in Kotlin are similar to those in Java, except that the new keyword is removed.

Inheritance and Constructors

Here's a simple example of inheritance:

package com.example.helloworld

open class Person {
    var name = ""
    var age = 0

    fun printInfo() {
        println("name is " + name + ",age is " + age)
    }
}

class Student:Person() {
    var grade = 0
}


fun main() {
    var tmp = Student()
    tmp.name = "Tom"
    tmp.age = 15
    tmp.grade = 3
}

In the above inheritance, there are two differences from the Java code:

  • open keyword: Use this keyword to declare that the class is inheritable, because the default class in Kotlin is final, that is, not inheritable, so you need to declare that the class can be inherited first
  • Inheritance form: In Java, the extends keyword is used to indicate the inheritance relationship, while in Kotlin, a colon: is used to indicate inheritance, and parentheses need to be added after the parent class

The above is just a simple example of inheritance in Kotlin, let's look at the constructor again here. There are two types of constructors in Kotlin: primary constructors and secondary constructors.

The main constructor has no function body and is defined directly after the class name. Each class will have a main constructor without parameters by default, and users can also explicitly specify parameters for it.

package com.example.helloworld

open class Person {
    var name = ""
    var age = 0

    fun printInfo() {
        println("name is " + name + ",age is " + age)
    }
}

class Student(val grade:Int):Person() {
}


fun main() {
    var tmp = Student(10)
    tmp.name = "Tom"
    tmp.age = 15
}

The above directly assigns attributes when building the Student object, and if you need to write some code in the main constructor, you need the init structure:

class Student(val grade:Int):Person() {
    init {
        println("grade is " + grade)
    }
}

This can explain why parentheses need to exist after the parent class in Kotlin class inheritance. According to the inheritance characteristics, the constructor of the subclass must call the constructor of the parent class, but the main constructor does not have a function body, so the main constructor of the subclass in Kotlin specifies which constructor of the parent class to call through parentheses.

That is to say, in the above content, it means that the primary constructor of the Student class will call the no-argument constructor of the Person class during initialization, and even in the case of lunch, the parentheses cannot be omitted.

package com.example.helloworld

open class Person(val name:String, val age:Int) {
    fun printInfo() {
        println("name is " + name + ",age is " + age)
    }
}

class Student(name:String, age:Int, val grade:Int):Person(name, age) {
    init {
        println("grade is " + grade)
    }
}

fun main() {
    var tmp = Student("Tom", 10,3)
}

The above form can call the parametric construction form of the parent class, but it needs to ensure that the form exists in the parent class.

In the parameter declaration of the Student class, the parameters of the parent class are not declared as val, because the parameters declared as val or var in the main constructor will automatically be called fields of this class, which will cause conflicts. Therefore, there is no need to add any keywords before the declaration of name and age in Student, so that its scope is limited to the main constructor.

However, multiple secondary constructors can exist in a class, and secondary constructors can also be used to instantiate a class. This is no different from the primary constructor, except that the secondary constructor has a function body.

Kotlin stipulates that when a class has both a primary constructor and a secondary constructor, all secondary constructors must call the primary constructor (indirect call):

package com.example.helloworld

open class Person(val name:String, val age:Int) {
    fun printInfo() {
        println("name is " + name + ",age is " + age)
    }
}

class Student(name:String, age:Int, val grade:Int):Person(name, age) {
    init {
        println("grade is " + grade)
    }

    constructor(name:String, age:Int):this(name, age, 0) {
    }

    constructor():this("",0) {
    }
}

fun main() {
    var tmp = Student("Tom", 10,3)
}

As can be seen from the above, the secondary constructor is defined through the sonstructor keyword. Two secondary constructors are defined above, and the primary constructor is called through the secondary constructor to complete the instantiation of the class object.

And when there are only secondary constructors in the class and no primary constructor:

package com.example.helloworld

open class Person(val name:String, val age:Int) {
    fun printInfo() {
        println("name is " + name + ",age is " + age)
    }
}

class Student:Person {
    constructor(name:String, age:Int):super(name, age) {
    }

    constructor():super("",0) {
    }
}

fun main() {
    var tmp = Student()
}

For example, in the above code, Student does not explicitly define the primary constructor, but there are two secondary constructors, that is, there is no primary constructor at this time, and since the subclass has no primary constructor, there is no need to add parentheses when inheriting the parent class . And since there is no primary constructor, the secondary constructor can only directly call the constructor of the parent class, so the super keyword is used.

interface

package com.example.helloworld

interface Study {
    fun readBook()
    fun doHomework()
}

open class Person(val name:String, val age:Int) {
    fun printInfo() {
        println("name is " + name + ",age is " + age)
    }
}

class Student(name:String, age:Int, val grade:Int):Person(name, age), Study {
    init {
        println("grade is " + grade)
    }
    override fun readBook() {
        println(name + " is reading.")
    }

    override fun doHomework() {
        println(name + " is doing Homework.")
    }
}

fun doStudy(study: Study) {
    study.readBook()
    study.doHomework()
}

fun main() {
    var tmp = Student("Tom", 10,3)
    doStudy(tmp)
}

In the above code, the parent class and interface are defined, and the parent class is inherited in the subclass to implement the interface. The extends key is used for inheritance in Java, the implements key is used for interface implementation, and commas are used for separation in Kotlin. At the same time, there is no need to add parentheses after the interface, because it has no constructor call.

The implementation of the method in the subclass interface should be declared with the override keyword, and doStudy is called in main, the doStudy interface is in the form of the Study interface, and then the function in the interface is called. This process is called polymorphism.

At the same time, Kotlin also supports the default implementation of the interface:

package com.example.helloworld

interface Study {
    fun readBook()
    fun doHomework() {
        println("do homework default implements")
    }
}

open class Person(val name:String, val age:Int) {
    fun printInfo() {
        println("name is " + name + ",age is " + age)
    }
}

class Student(name:String, age:Int, val grade:Int):Person(name, age), Study {
    init {
        println("grade is " + grade)
    }
    override fun readBook() {
        println(name + " is reading.")
    }
}

fun doStudy(study: Study) {
    study.readBook()
    study.doHomework()
}

fun main() {
    var tmp = Student("Tom", 10,3)
    doStudy(tmp)
}

In the definition of the above interface, there is a function body in doHomework, because the interface implementation only requires the implementation of readBook, but does not require the implementation of doHomework, so the subclass Student calls the default implementation of the interface.

In the above code, properties and methods are not isolated, that is, variables and methods are not modified with visibility modifiers, while there are four modifiers in Java: public, private, protected, and default to achieve encapsulation and isolation.

In Kotlin, there are also four modifiers, namely public, private, protected, and internal. When you need to use which modifier, you can define it directly before the fun keyword.

  • private: Indicates that it is only visible inside the current class
  • public: indicates that it is visible to all classes, and public is the default item in Kotlin
  • protected: Indicates that it is only visible to the current class and subclasses
  • internal: Indicates that it is only visible to classes in the same module

Data classes and singleton classes

In project development, data classes usually need to rewrite equals, hashCode, toString and other methods, but in fact these codes are extremely repetitive.

In Kotlin, you can directly use the following code to create a data class:

package com.example.helloworld

data class Cellphone(val brand:String, val price:Double)

fun main() {
    val Cellphone1 = Cellphone("HW", 5648.5)
    val Cellphone2 = Cellphone("HW", 5648.5)
    println(Cellphone1)
    println(Cellphone1 == Cellphone2)
}

In the above code, using data to declare a class will automatically generate equals, hashCode, toString and other methods to reduce repetitive development work.

At the same time, when there is no code in a class, the curly braces at the end can be omitted.

In Kotlin, the way to create a singleton class is also very simple, just change the class keyword to the object keyword:

package com.example.helloworld

object Singleton {
    fun singletonTest() {
        println("Test")
    }
}
fun main() {
    Singleton.singletonTest()
}

The above code also realizes the singleton mode. Kotlin will automatically create a Singleton instance and ensure that only one Singleton instance exists globally.

Lambda

Collection creation and traversal

If users want to create an instance of a string list, they may need to construct an ArrayList<string>, and then add strings one by one, but this method may be cumbersome, and Kotlin specifically provides a built-in listOf function to simplify the process:

package com.example.helloworld

fun main() {
    var tmp = listOf("apple","banana","orange")
    for(item in tmp) {
        println(item)
    }
}

The above code uses listOf to create a list of strings, and then prints them one by one through the for loop. However, the listOf function creates an immutable collection, that is, it can only be used for reading and cannot be modified.

And if you need to create a modifiable collection, you need to use the mutableListOf function:

package com.example.helloworld

fun main() {
    var tmp = mutableListOf("apple","banana","orange")
    tmp.add("pear")
    for(item in tmp) {
        println(item)
    }
}

The above mentioned is the usage of List, and the usage of Set collection is roughly similar, but the function name is changed to setOf and mutableSetOf. However, only elements are stored in the Set collection, that is, there are no duplicate elements.

Before adding the Map, create an instance of HashMap<key, value>, and then add the key-value pair data to the Map, and Kotling can also use the form of map["key"] = value to assign directly:

package com.example.helloworld

fun main() {
    var tmp = HashMap<String,Int>()
    tmp["apple"] = 10
    tmp["banana"] = 20
    tmp["orange"] = 30

    for ((key, value) in tmp) {
        println("key is " + key + ",value is " + value)
    }
}

This way of writing also looks a bit cumbersome. In Kotlin, you can also use the mapOf and mutableMapOf functions to simplify the process:

package com.example.helloworld

fun main() {
    var tmp = mapOf("apple" to 10, "banana" to 20, "orange" to 30)

    for ((key, value) in tmp) {
        println("key is " + key + ",value is " + value)
    }
}

This looks much simpler.

Functional API for collections

Here we mainly look at the grammatical structure of Lambda expressions.

For example, to find the element with the longest string in a list:

package com.example.helloworld

fun main() {
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = ""

    for (item in tmp) {
        if (item.length > maxLengthFruit.length) {
            maxLengthFruit = item
        }
    }

    println("max length fruit is " + maxLengthFruit)
}

The code above works, but if you use the functional API of collections, you have:

package com.example.helloworld

fun main() {
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNull { it.length }

    println("max length fruit is " + maxLengthFruit)
}

In layman's terms, Lambda is a small piece of code that can be passed as a parameter, and its grammatical structure is:

{param1:type,param2:type -> func}

In the above definition:

  • param: Indicates incoming parameters
  • type: Indicates the parameter type
  • func: Indicates the function body

For example, the above code is essentially:

package com.example.helloworld

fun main() {
    var tmp = listOf("apple","banana","orange")
    var lambda = {fruit:String -> fruit.length}
    var maxLengthFruit = tmp.maxByOrNull(lambda)

    println("max length fruit is " + maxLengthFruit)
}

That is, maxByOrNull is an ordinary function that can receive a parameter in the form of Lambda, and when traversing the collection, pass the value of each traversal as a parameter to the Lambda expression, and then find its maximum value.

The above code can of course also be written like this:

package com.example.helloworld

fun main() {
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNull({fruit:String -> fruit.length})

    println("max length fruit is " + maxLengthFruit)
}

And Kotlin stipulates that when the last parameter of a Lambda parameter function, the Lambda expression can be moved outside the function brackets:

package com.example.helloworld

fun main() {
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNull(){fruit:String -> fruit.length}

    println("max length fruit is " + maxLengthFruit)
}

And if the Lambda expression is the only parameter of the function, the function parentheses can be omitted:

package com.example.helloworld

fun main() {
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNull{fruit:String -> fruit.length}

    println("max length fruit is " + maxLengthFruit)
}

And Kotlin has a type deduction mechanism, so the parameter list in the Lambda expression does not need to declare the parameter type in most cases:

package com.example.helloworld

fun main() {
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNull{fruit -> fruit.length}

    println("max length fruit is " + maxLengthFruit)
}

And when there is only one parameter in the parameter list of the Lambda expression, there is no need to declare the parameter name, but the it keyword can be used instead:

package com.example.helloworld

fun main() {
    var tmp = listOf("apple","banana","orange")
    var maxLengthFruit = tmp.maxByOrNull{it.length}

    println("max length fruit is " + maxLengthFruit)
}

This is how the original form was formed.

Here are a few other APIs:

package com.example.helloworld

fun main() {
    println(listOf("apple","banana","orange").map{it.uppercase()})
    println(listOf("apple","banana","orange").filter{it.length > 5})
    println(listOf("apple","banana","orange").any{it.length > 5})
    println(listOf("apple","banana","orange").all{it.length > 5})
}
  • map: used to map and transform the list
  • filter: used to conditionally filter the list
  • any: Determine whether there is an element in the list that satisfies the condition
  • all: Determine whether all elements of the list satisfy the condition

Use of Java functional API

If a Java method is called in Kotlin code, and the method receives a Java single abstract method interface parameter, a functional API can be used. A Java single abstract method interface means that there is only one method to be implemented in the interface. If there are multiple methods to be implemented in the interface, the functional API cannot be used.

For example, there is a common single abstract method interface Runnable in Java native API, and there is a run method to be implemented in this interface:

public interface Runnable {
    void run();
}

As mentioned earlier, for any Java method, as long as it receives the Runnable parameter, you can use the functional API. Runnable is mainly used in combination with threads. Let's take a look at Java's thread class Thread:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("Thread is running!")
    }
}).start();

The above uses the writing method of the anonymous class, creates the anonymous class instance of the Runnable interface, and passes it to the constructor of the Thread class, and then calls the start method of the Thread class to execute the thread.

And written in Kotlin code, it is:

package com.example.helloworld

fun main() {
    Thread(object :Runnable {
        override fun run() {
            println("Thread if running")
        }
    }).start()
}

The form of the above code is similar, but the way of writing is slightly different. This is because Kotlin discards the new keyword, so the object keyword is used instead to create an anonymous class. If you meet the usage conditions of Java functional API, you can simplify the code:

package com.example.helloworld

fun main() {
    Thread(Runnable {
        println("Thread if running")
    }).start()
}

In the above code, Kotlin will understand that the Lambda expression behind Runnable is the content to be implemented in the run method. At the same time, if the parameter list of the Java method has one and only one Java single abstract method interface function, the interface name can be omitted:

package com.example.helloworld

fun main() {
    Thread{ println("Thread if running") }.start()
}

In this case, the original form of the click event interface OnClickListener commonly used in Android is:

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        
    }
});

can be simplified to:

button.setOnClickListener{ };

null pointer check

Nullable type checking

Take a look at the previous code:

fun doStudy(study: Study) {
    study.readBook()
    study.doHomework()
}

The above code does not perform empty judgment processing.

In actual development, most of the methods of the Java code need to be processed first to ensure that the subsequent processing is carried out normally. And such checks will lead to more cumbersome code writing. However, Kotlin can almost eliminate null pointer exceptions by using compile-time null checks.

Still the above code, although there is no difference in form from the Java code, but in fact there is no risk of null pointers, because Kotlin defaults that all parameters and variables are not nullable, that is, the parameter study passed in above must not be null , so the above function can be called normally.

That is, Kotlin advances the check of null pointer exceptions to the compilation period, that is, there is a risk of null pointer exceptions during compilation, which will cause compilation errors instead of postponing the errors to the code execution period, so as to ensure that there will be no null pointers during the running of the program abnormal.

And if all parameters and variables are not nullable, then when a parameter or variable is really required to be nullable, another nullable type system provided by Kotlin is required.

This type system is to add a question mark after the class name. For example, in the above code, adding a question mark after Study means that the parameter study can be empty at this time, but at this time, it needs to be judged as empty:

fun doStudy(study: Study?) {
    if (study != null) {
        study.readBook()
        study.doHomework()
    }
}

Auxiliary tool for empty judgment

In empty judgment processing, if you use if processing, it will make the code more cumbersome, so Kotlin provides a series of auxiliary tools.

The first and most commonly used is the ?. operator, which calls the corresponding method normally when the object is not empty, and does nothing when the object is empty:

fun doStudy(study: Study?) {
    study?.readBook()
    study?.doHomework()
}

 There is also the ?: operator, which accepts an expression on both sides of the operator. If the result of the left expression is not empty, it returns the result of the left expression, otherwise it returns the result of the right expression:

fun getTextLength(text: String?):Int {
    if (text != null) {
        return text.length
    } else {
        return 0
    }
}

If you use the auxiliary empty judgment tool, it can be written as:

fun getTextLength(text: String?) = text?.length?:0

It can be said to be quite concise.

Another example is the following code:

package com.example.helloworld

var content: String?="hello"

fun printUpperCase() {
    val upperCase = content.toUpperCase()
    println(upperCase)
}

fun main() {
    if (content != null) {
        printUpperCase()
    }
}

Although the empty judgment process has been performed in the main function, it is not clear that the process has been performed externally in printUpperCase, so it still cannot be compiled.

At this time, if you want to force the compilation, you can use the non-empty assertion tool, which is written by adding !! after the object:

fun printUpperCase() {
    val upperCase = content!!.toUpperCase()
    println(upperCase)
}

The last auxiliary tool is let. let is neither an operator nor a keyword, but a function that provides a functional API programming interface and passes the original calling object as an object into the Lambda expression, such as:

obj.let {obj2 -> func}

In the above example, the let function of the obj object is called, then the code in the Lambda expression will be executed immediately, and the obj object itself will be passed as a parameter to the Lambda expression. obj2 just changed the parameter name, and it is actually the same object as obj.

The combined operation of let function and null judgment processing is:

fun doStudy(study: Study?) {
    study?.let {
        it.readBook()
        it.doHomework()
    }
}

In this way, the let function is combined with the null judgment processing, instead of using the ?. operator for every interface method call.

At the same time, the let function can handle the null judgment problem of global variables, while if will report an error because the value of the global variable will be changed by other threads.

miscellaneous

string embedded expression

Kotlin allows you to embed an expression in the ${} syntax structure in a string, and replace the content with the result of the expression execution at runtime:

"hello,${obj.name}. nice to meet you"

And when there is only one variable name in the expression, the curly braces on both sides can also be omitted:

"hello,$name. nice to meet you"

Function parameter default values

The sub-constructor was mentioned before, but the sub-constructor is rarely used because Kotlin can set default values ​​​​for parameters.

fun printParams(num:Int, str:String = "hello") {
    println("num is $num, str is $str")
}

In this way, when calling, you can only pass in the num variable and assign a default value to str.

And if you swap the position of the default value:

fun printParams(num:Int = 10, str:String) {
    println("num is $num, str is $str")
}

At this time, you can’t just write one parameter when calling, but use the form of key-value pair to match, that is:

printParams(str = "hello")

In the form of key-value pairs, you don’t need to care about the order of parameters, because they are matched by key-value pairs, so naturally there will be no type mismatch.

In this way, the default parameter value can replace the function of the secondary constructor in some scenarios.

Guess you like

Origin blog.csdn.net/SAKURASANN/article/details/126802002
Recommended