preface

Two years after Google announced at I/O 2017 that Kotlin was officially a first-level development language for Android, on a par with Java, and was fully supported by AndroidStudio, At THE 2019 I/O conference, Google announced that Kotlin has become the first language for Android development. Although Java is still available, Google recommends that we use Kotlin to develop Android applications. The Kotlin version will also be given priority in the official Api provided later, and Kotlin is basically used by developers to write technical blogs and third-party libraries. In addition, it is important and urgent to learn Kotlin programming.

Kotlin introduction

A modern programming languagethat makes happier. Kotlin is a modern programming language that makes developers happier. Developed and designed by JetBrains, it is also a STATIC language based on the JVM.

The problem

There will be some questions in my mind when learning Kotlin 🤔️?

The Android operating system is developed by Google. Why does JetBrains, a third-party company, design a language to develop Android applications?

Because the Java Virtual Machine (or ART in Android) doesn’t work directly with the source code of the language you write, but with the class bytecode files you compile. Jvm-based languages such as Kotlin, Groovy, etc., all have their own compiler that compiles the source files into class bytecode files. The Java Virtual Machine doesn’t care where the class bytecode files were compiled from, as long as the class bytecode files meet the specification, it can recognize them. That’s why JetBrains was able to design a programming language for Android applications as a third-party company

2. Why does Google recommend Kotlin when Java is used to develop Android applications?

There are many reasons for this, but the main ones are:

  • 1) Kotlin syntax is more concise, and the amount of code developed with Kotlin can be 50% or more less than that developed with Java
  • 2) Kotlin’s grammar is more advanced. Compared with the old Java syntax, Kotlin adds many modern high-level language syntax features, which greatly improves our development efficiency
  • 3) Kotlin is 100% compatible with Java. Kotlin can call Java code directly, and can seamlessly use Java third-party open source libraries, which makes Kotlin inherit all the wealth of Java while adding many new features

3. Why does Kotlin show that a non-abstract class is declared inheritable, instead of a class defined in Java that is inheritable by default?

Because a class is inheritable by default, it has no way of knowing how subclasses will implement it, so there are some unknown risks. The same goes for the val keyword. In Java, a variable is mutable unless you actively declare the final keyword for it. As the complexity of a project increases and many people collaborate, you never know when a mutable variable will be modified by someone, even if it shouldn’t be. It’s also hard to identify problems. So Kotlin’s designs are designed to make the program more robust and conform to the specifications of high-quality coding

Now let’s get into Kotlin’s study

Attached is a mind map for learning Kotlin

Note: Kotlin is now the first Android development language, and AndroidStudio, Google’s own son, has perfect support for Kotlin, so all the demo code below is running on AndroidStudio

1. Variables and functions

1, variables,

1) Use the keyword val (short for value) to declare an immutable variable, i.e., a read-only variable, which cannot be reassigned after initial assignment

2) Use the var (short for variable) keyword to declare a variable, i.e., a read-write variable, which is assigned to an initial value and can be re-assigned to a value, as in Java’s non-final variables

3) Every line of code in Kotlin does not need to be added;

// In Java, we would define it this way
int a = 10;
boolean b = true

// In Kotlin, we can define that when a variable is assigned, the Kotlin compiler does type derivation
// Define an immutable variable a
val a = 10
// Define a variable b
var b = true

// If we specify a type for the variable, Kotlin will not do type derivation
val a: Int = 10
var b: Boolean = "erdai"
Copy the code

If you look closely, you’ll notice that Kotlin defines variables to display the specified type using an Int, Boolean, whereas Java uses a lowercase Int, Boolean, which indicates: Kotlin completely abandons the basic data types in Java and is all about object data types. Here is a comparison of Java and Kotlin data types:

Java basic data types Kotlin object data type Data Type Description
byte Byte Byte type
short Short Short integer
int Int The integer
long Long Long integer
float Float Single-precision floating point number
double Double Double floating-point number
char Char character
boolean Boolean The Boolean

2, constant

To define a constant in Kotlin requires three conditions

Const val = const val; const val = const val

2) Only string and underlying object types can be modified

3. Only top-level constants, members of object, and members of companion Objects can be modified. These concepts will be covered later

// Define a top-level constant that is not placed in any class
const val CONSTANT = "This is a constant"

// Define a singleton class with an object modifier. The class defines a constant
object SingeTon {
    const val CONSTANT = "This is a constant"
}

class KotlinPractice {
    // Define a companion object with a constant in it
    companion object{
        const val CONSTANT = "This is a constant"}}Copy the code

3, functions,

1) Functions and methods are the same concept. In Java, we call them methods, but in Kotlin, we call them functions.

2) A function is a carrier to run code, like the main function we used is a function

The rules that define the syntax in Kotlin:

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

// The following two methods have the same effect
fun methodName1(params: Int,params2: Int): Unit{}fun methodName1(params: Int,params2: Int){}Copy the code

Syntax explanation of the above functions:

  • Fun (short for function) is the keyword that defines a function. Whatever function you define, declare it with fun, okay
  • Function names can be arbitrary, just as they are defined in Java
  • The function name can have any number of parameters. The parameters are declared in the following format: “parameter name” : “parameter type”
  • This part is optional. If not defined, the default value is Unit, and the Unit can be omitted

Put this into practice:

fun main(a) {
    val number1 = 15
    val number2 = 20
    val maxNumber = largeNumber(number1,number2)
    println(maxNumber)
}

fun largeNumber(number1: Int,number2: Int) : Int{
    // Call the top-level Max function to calculate the maximum of the two
    return max(number1,number2)
}

// Print the result
20
Copy the code

Kotlin syntax sugar: When there is only one line of code in the body of a function, we can not write the body of the function. Instead, we can write the only line of code at the end of the function definition, concatenated with =

Let’s change the function largeNumber:

// We omit the {} and return keywords in the function body, and add or subtract =
fun largeNumber(number1: Int,number2: Int) : Int = max(number1,number2)
// We can also omit the return value of the function according to the Kotlin type derivation mechanism
fun largeNumber(number1: Int,number2: Int) = max(number1,number2)
Copy the code

Second, the logic control of the program

1. If statement

1) Kotlin’s if statement inherits all the properties of Java’s if statement except that it can use the last line of code in each condition as the return value

Let’s modify the largeNumber function’s internal implementation:

//Kotlin takes the last line of code in each condition as the return value
fun largeNumber(number1: Int,number2: Int) : Int{
    return if(number1 > number2){
      	number1
    }else {
      	number2
    }
}

// Using the syntax sugar and Kotlin type derivation mechanism we learned above, we can also abbreviate the largeNumber function, which ends up like this
fun largeNumber(number1: Int,number2: Int) = if(number1 > number2) number1 else number 2
Copy the code

2, when conditional statement

Consider the Switch statement in Java, which doesn’t work very well:

1) Switch statements can only support certain types, such as integers, shorter than integers, strings, and enumerations. If we are using none of these types, the Switch is not available

2) The case condition of the Switch statement should always end with a break

These issues are addressed in Kotlin, and Kotlin adds a number of powerful new features:

1) When conditional statements also have a return value. Similar to if conditional statements, the last line of code in the condition is the return value

2) The when condition allows arguments of any type to be passed

3) when conditional format: match value -> {execute logic}

4) The when conditional statement is the same as the if conditional statement. When there is only one line of code in the condition body, the {} of the condition body can be omitted

// If there is a parameter in when
fun getScore(name: String) = when (name) {
    "tom" -> 99
    "jim" -> 80
    "lucy" -> 70
    else -> 0
}

// If there is no parameter in when, use the == operator in Kotin to determine whether the string or object is equal
fun getScore(name: String) = when {
    name == "tom" -> 99
    name == "jim" -> 80
    name =="lucy" -> 70
    else -> 0
}
Copy the code

3. Loop statements

There are two main types of cycles:

1) while loop, which is no different from Java

2), for loop, Java commonly used loop: for- I, for-each, Kotlin is mainly: for-in

interval

1) Use… Creates an ascending interval with both ends closed

2) Use until to create an ascending interval with a closed interval on the left and open interval on the right

3) Use downTo to create a descending interval where both ends are closed

4) Add step at the end of the interval to indicate that several elements are skipped

// Note: In Kotlin, you can use inline string expressions, that is, you can refer to variables in a string, as we'll see later
//情况1
fun main(a) {
    / / to use.. Creates an ascending interval with both ends closed
    for (i in 0.10.){
        print("$i ")}}// Print the result
0 1 2 3 4 5 6 7 8 9 10

//情况2
fun main(a) {
    // Use until to create an ascending interval with a closed interval on the left and an open interval on the right
    for (i in 0 until 10){
        print("$i ")}}// Print the result
0 1 2 3 4 5 6 7 8 9

//情况3
fun main(a) {
    // Use downTo to create descending intervals where both ends are closed
    for (i in 10 downTo 0){
        print("$i ")}}// Print the result
10 9 8 7 6 5 4 3 2 1 0

//情况4
fun main(a) {
    // Use downTo to create descending intervals with both ends closed, skipping three elements at a time
    for (i in 10 downTo 0 step 3){
        print("$i ")}}// Print the result
10 7 4 1 
Copy the code

Object-oriented programming

Understanding of object-oriented programming: Object-oriented language can create the class, class is a wrapper for things, for example, car we can encapsulate them into class, the class name is usually a noun, the class has its own field and function, field represents the class with attributes, is usually a noun, like people can have your name and age, cars can have brand and price, Function according to the class with those behaviors, generally as a verb, like people to eat sleep, the car can drive and maintain, through the encapsulation of this, we can create these classes where appropriate, and then call their fields and function to meet the demand of the actual programming, it is the most basic object-oriented programming ideas

1. Classes and Objects

We use AndroidStudio to create a Person Class. In the dialog box that appears, enter Person and select Class. By default, the dialog box automatically selects to create a File, which is usually used to write Kotlin top-level functions and extension functions.

When we create properties in a class, Kotlin automatically creates get and set methods for us

2) Kotlin instantiates objects similar to Java, but without the new keyword

3) In a class, we use the var keyword to define a property. Since properties are usually mutable, we use val if you are sure that a property does not need to be changed

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

    fun sleep(a){
        println("$name is sleep, He is $age years old.")}}fun main(a) {
    val person = Person()
    person.name = "erdai"
    person.age = 20
    person.sleep()
}
// Print the result
erdai is sleep, He is 20 years old.
Copy the code

Inheritance and constructors

inheritance

1. Kotlin states that if you want to declare a non-abstract class inheritable, you must add the open keyword, otherwise it is not inheritable. This is unlike Java, where classes are inheritable by default, according to Effective Java: If a class is not specifically designed for inheritance, then it should be actively declared final, disallowing it from being inherited

2) Inheritance and implementation in Kotlin are represented by:

// Declare that the Person class can be inherited
open class Person {
    var name = ""
    var age = 0
  
    fun sleep(a) {
        println("$name is sleep, He is $age years old.")}}// Define Student to inherit from Person class
// Why does Person have a parenthesis after it? Because a subclass's constructor must call the superclass's constructor, in Java, the subclass's constructor is implicitly called
class Student : Person(){
    
}
Copy the code

The constructor

1) The main constructor has no body, just after the name of the class. If you need to do logic in the main constructor, you can copy the init function

Arguments declared as val or var in the primary constructor automatically become fields of the class. If not, then the scope of the field is limited to the primary constructor

3) Sub-constructors are defined via the constructor keyword

When a class does not have a primary constructor, but a secondary constructor is defined, then the inherited class does not need to add ()

// Define the Student class, define the primary constructor, define the properties sno and grade, and inherit from the Person class
class Student(var sno: String, var grade: Int) : Person() {
        // Do some initialization logic
  	init {
        name = "erdai"
        age = 20
    }
    
    // Declare a subconstructor that takes one argument
    constructor(sno: String): this(sno,8){

    }

    // Declare a subconstructor that takes no arguments
    constructor() :this("123".7){

    }

    fun printInfo(a){
        println("I am $name.$age yeas old, sno: $sno, grade: $grade")}}fun main(a) {
    val student1 = Student()
    val student2 = Student("456")
    val student3 = Student("789".9)
    student1.printInfo()
    student2.printInfo()
    student3.printInfo()
}
// Print the result
I am erdai, 20 yeas old, sno: 123, grade: 7
I am erdai, 20 yeas old, sno: 456, grade: 8
I am erdai, 20 yeas old, sno: 789, grade: 9

// A special case: when a class does not explicitly define a primary constructor, but does define a secondary constructor, then the inherited class does not need to be followed by ()
class Student : Person{
    constructor() : super() {}}Copy the code

3, interfaces,

1) There is no difference between defining interfaces in Kotlin and Java

Kotlin added a default implementation for functions defined in an interface, and Java started to support this functionality after JDK1.8
interface Study{
     fun readBooks(a)
     // If the subclass does not override the method, then the default implementation of the method is called
     fun doHomework(a){
         println("do homework default implementation")}}// Define an inheritable People class with two attributes: name and age
open class People(val name: String,val age: Int){

}

// Define a Student class that extends from the People class to implement the Study interface
class Student(name: String, age: Int) : People(name, age),Study{
    override fun readBooks(a) {
        println("$name is read book")}}// a method is then called in the main function
fun doStudy(study: Study){
    study.readBooks()
    study.doHomework()
}

// Call the main function
fun main(a){
    val student = Student("erdai".20)
    // Here student implements the Study interface, which is called interface-oriented programming, or polymorphism
    doStydy(student)
}

// Print the result
erdai is read book
do homework default implementation

Copy the code

4. Visibility modifier for functions

The modifier Java Kotlin
public All classes visible All classes visible (default)
private Current class visible Current class visible
protected Current class, subclass, visible under the same package Current class and subclass are visible
default Visibility under the same package (default) There is no
internal There is no Classes in the same module are visible

5. Data classes and singletons

Data classes

In Java, data classes often need to override equals(), hashCode(), and toString(), the equals() method used to determine whether two data classes are equal. The hashCode() method, as a companion to equals(), also needs to be overridden, otherwise the system classes related to the hash will not work properly, and the toString() method is used to provide clearer input logging, otherwise a data class will print a row of memory addresses by default

2) In Kotlin, we just need to decorate a class with the data keyword, and Kotlin will automatically generate the methods that Java needs to override

// In Java, we would write this
public class Cellphone {
    
    String brand;
    double price;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null|| getClass() ! = o.getClass())return false;
        Cellphone cellphone = (Cellphone) o;
        return Double.compare(cellphone.price, price) == 0 &&
                Objects.equals(brand, cellphone.brand);
    }

    @Override
    public int hashCode() {
        return Objects.hash(brand, price);
    }

    @Override
    public String toString() {
        return "Cellphone{" +
                "brand='" + brand + '\' ' +
                ", price=" + price +
                '} '; }}// In Kotlin, you will find it is so concise
data class Cellphone(val brand: String, val price: Double)
Copy the code

Singleton class

1) Kotlin defines a singleton class by replacing the class keyword with the object keyword

2) Calling the method in the singleton class is also relatively simple, directly using the class name. The method can be used, similar to Java static method invocation

// Java singleton lazybone
public class Singleton{
  	private static Singleton instance;
  
  	public synchronized static Singleton getInstace() {
      		if(instance == null){
            	instance = new Singleton();
          }
      		return instance;
    }
  
  	public void singleonTest(){
      	System.out.println("singletonTest in Java is called."); }}// Singletons in Kotlin
object Singleton{
  	fun singletonTest(a){
      	println("singletonTest in Kotlin is called.")}}fun main(a) {
    Singleton.singletonTest()
}
// Print the result
singletonTest in Kotlin is called.
Copy the code

Lambda programming

Kotlin has supported Lambda programming since the first version, and Lambda expressions in Kotlin are extremely powerful. In this chapter, we will learn some basics of Lambda programming:

1) In simple terms, a Lambda is a piece of code that can be passed as an argument. It can be used as an argument to a function, return a value, or be assigned to a variable

{parameter 1: parameter type, parameter 2: parameter type -> function body}

3) Many times, we will use the simplified form of the syntax structure, which is simply a function body: {function body}. In this case, when the argument list of a Lambda expression has only one argument, we can omit the argument. By default, there will be an IT argument

4) Kotlin specifies that we can move the Lambda expression outside the function parentheses when the expression is the last argument to the function

5) Kotlin specifies that when a Lambda expression is the only argument to a function, the parentheses of the function can be omitted

1. Collection creation and traversal

1) Immutable set: After the set is initialized, we cannot add, delete or modify it

2) Variable set: After the set is initialized, we can add, delete and modify it

Immutable set Variable set
listOf mutableListOf
setOf mutableSetOf
mapOf mutableMapOf
/ / the List collection
// Define an immutable List
val list1 = listOf("Apple"."Banana"."Orange"."Pear"."Grape")
// Define a mutable List set
val list2 = mutableListOf("Apple"."Banana"."Orange"."Pear"."Grape")
// Add the element
list2.add("Watermelon")
for (i in list2) {
    print("$i ")}// Print the result
Apple Banana Orange Pear Grape Watermelon

//Set and List are used exactly the same way
// Define an immutable Set
val set1 = setOf("Apple"."Banana"."Orange"."Pear"."Grape")
// Define a variable Set
val set2 = mutableSetOf("Apple"."Banana"."Orange"."Pear"."Grape")
// Add the element
set2.add("Watermelon")
for (i in set2) {
    print("$i ")}// Print the result
Apple Banana Orange Pear Grape Watermelon

/ / Map collections
// Define an immutable set of maps
val map1 = mapOf("Apple" to 1."Banana" to 2."Orange" to 3."Pear" to 4."Grape" to 5)
// Define a mutable set of maps
val map2 = mutableMapOf("Apple" to 1."Banana" to 2."Orange" to 3."Pear" to 4."Grape" to 5)
// If the current key exists, modify the element; if the current key does not exist, add the element
map2["Watermelon"] = 6
for ((key,value) in map2) {
    print("$key: $value ")}// Print the result
Apple: 1 Banana: 2 Orange: 3 Pear: 4 Grape: 5 Watermelon: 6 
Copy the code

2. Functional APIS for collections

// Define an immutable List
val list1 = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
// Now I want to print the longest string of the set Chinese and English names, we can do so
1 / / way
var maxLengthFruit = ""
for (fruit in list1) {
    if(fruit.length > maxLengthFruit.length){
        maxLengthFruit = fruit
    }
}
print(maxLengthFruit)
// Print the result
Watermelon

// But it's easier to use a functional Api, where the maxBy function iterates to the maximum value of your condition
2 / / way
val maxLengthFruit = list1.maxBy {
    it.length
}
print(maxLengthFruit)
// Print the result
Watermelon

// By combining the maxBy function with the syntax structure of a Lambda expression, let's analyze the principle of writing method 2, as shown below
/ / 1
val list1 = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
val lambda = {fruit: String -> fruit.length}
// The maxBy function actually takes an argument of the function type, as we'll see later in higher-order functions, which means we can pass in a Lambda expression
val maxLengthFruit = list1.maxBy(lambda)

Replace the lambda / / 2
val maxLengthFruit = list1.maxBy({fruit: String -> fruit.length})

//3 Kotlin states that we can move the Lambda expression outside the function parentheses when it is the last argument to the function
val maxLengthFruit = list1.maxBy(){fruit: String -> fruit.length}

//4 Kotlin specifies that when the Lambda expression is the only argument to the function, the parentheses of the function can be omitted
val maxLengthFruit = list1.maxBy{fruit: String -> fruit.length}

//5 When there is only one argument in the argument list of a Lambda expression, we can omit the argument. By default, there is an it argument
val maxLengthFruit = list1.maxBy{ it.length }

// After the above steps 1->2->3->4->5, we finally get this way of writing 5
Copy the code

There are many more such functional apis in the list collection. Let’s use the list collection to practice some other functional apis:

val list = listOf("Apple"."Banana"."Orange"."Pear"."Grape"."Watermelon")
/ / 1
// Use the map operation to map an element to a new element
val newList = list.map{
  	it.toUpperCase()
}
for (s in newList) {
    print("$s ")}// Print the result
APPLE BANANA ORANGE PEAR GRAPE WATERMELON 

/ / 2
// Run the filter operation to filter the string whose length is less than or equal to 5
val newList = list.filter {
    it.length <= 5
}
for (s in newList) {
    print("$s ")}// Print the result
Apple Pear Grape
Copy the code

3. Use of Java functional apis

A functional Api can also be used to call Java methods in Kotlin, but only if the interface is written in Java and the interface has only one method to implement

2) Using anonymous inner classes in Kotlin is a little different from Java. Instead of using the new keyword, you can use the object keyword

// An anonymous inner class in Java
new Thread(new Runnable() {
     @Override
     public void run() {

     }
}).start();

// In Kotlin, you could write it this way
Thread(object : Runnable{
    override fun run(a) {
            
    }
}).start()

Since Runnable has only one method to implement, even if there is no overwrite run() shown here, * Kotlin also understands that the following Lambda expression is what is to be implemented in the run() method */
Thread(Runnable{
  
}).start()

// Since the interface is a single abstract method, we can omit the interface name
Thread({
  
}).start()

// We can move the Lambda expression outside the function parentheses when it is the last argument to the function
Thread(){
  
}.start()

// When the Lambda expression is the only argument to the function, the parentheses of the function can be omitted
Thread{
  
}.start()
Copy the code

Null pointer check

The highest type of exception that crashes on Android is NullPointerException. The main reason for this is that NullPointerException is a runtime exception that is not checked by the programming language and can only be avoided by the programmer through logical judgment. But even among good programmers, It is also impossible to account for all potential null-pointer exceptions. Kotlin solves this problem by checking null-pointer exceptions at compile-time. This almost eliminates null-pointer exceptions, but it makes the code harder to write, but Kotlin provides a number of tools to help. So that we can easily deal with a variety of empty situations, let’s learn it

1, can empty type system and empty auxiliary tools

1) Add? By default, all parameters and variables in Kotlin cannot be null

2) When an object is called, use? The.operator, which means to call if the current object is not null, which means to do nothing

And 3)? The: operator means to return the left result if it is not empty, or return the right result otherwise

4) add!! The operator tells Kotlin that this is definitely not null, you don’t have to check, and if it is, it throws a null pointer exception

5) the let function, which provides a functional Api and passes the currently called object as an argument to the Lambda expression

Case 1: After the type, add? By default, all parameters and variables in Kotlin cannot be null

interface Study{
    fun readBooks(a)
    fun domeHomework(a){
        println("do homework default implementation")}}fun doStudy(study: Study){
    study.readBooks()
    study.domeHomework()
}
Copy the code

If you try to pass null to the doStudy method, the compiler will report an error:

So in this case we can use the nullable, Study instead of Study, right? , as shown below:

The Kotlin compiler does not allow this to happen. If doStudy accepts a nullable type parameter, it may result in an internal null pointer. The Kotlin compiler does not allow this to happen.

fun doStudy(study: Study?).{
    if(study ! =null){
        study.readBooks()
        study.domeHomework()
    }
}
Copy the code

Case 2: When the object is invoked, use? The.operator, which means to call if the current object is not null, which means to do nothing

For the above doStudy method, we can do the following:

fun doStudy(study: Study?).{ study? .readBooks() study? .domeHomework() }Copy the code

Situation 3:? The: operator means to return the left result if it is not empty, or return the right result otherwise

// We might write code like this
val a = if(b ! =null) {
    b
} else {
    c
}

/ / use? The: operator can be simplified as follows
vala = b ? : cCopy the code

Case 4: Add!! The operator tells Kotlin that this is definitely not null, you don’t have to check, and if it is, it throws a null pointer exception

// The following code fails to compile because the name in the printName method does not know that you made a non-null judgment on the outside
val name: String? = "erdai"

fun printName(a){
    val upperCaseName = name.toUpperCase()
    print(upperCaseName)
}

fun main(a) {
    if(name ! =null){
       printName()
    }
}

// So in the case of the above explicitly not null, we can use!! Operator, modify the printName method
// Also remind yourself if there is a better way to do this, because there is still a potential null pointer exception with this operator
fun printName(a){
    valupperCaseName = name!! .toUpperCase() print(upperCaseName) }// Print the result
ERDAI
Copy the code

Case 5: The let function, which provides a functional Api and passes the currently called object as an argument to the Lambda expression

// This is the way we implemented case 2, but it can be very verbose if there are many ways to call it, such as:
fun doStudy(study: Study?).{ study? .readBooks() study? .domeHomework() study? .a() study? .b() }// This is equivalent to the following code:
fun doStudy(study: Study?).{
    if(study ! =null){ study? .readBooks() }if(study ! =null){ study? .domeHomework() }if(study ! =null){ study? .a() }if(study ! =null){ study? .b() } }// At this point, we can use the let function to operate
fun doStudy(study: Study?).{ study? .let{ it.readBooks() it.domeHomework() it.a() it.b() } }Copy the code

Five, Kotlin magic tricks

1. Inline expressions for strings

${} can be used to refer to a variable value or an expression in a string. If there is only one variable in {}, {} can also be removed

fun main(a) {
    val a = "erdai"
    val b = "666"
    print("$a ${a + b}")}// Print the result
erdai erdai666
Copy the code

2. Default values for function parameters

1) When defining a function, we can add a default value to the parameter of the function, so we don’t need to pass the parameter

2) When we call a function, we can pass the parameters in the form key value

// Case 1: When we define a function, we can add a default value to the parameter of the function, so we don't need to pass the parameter
fun printParams(name: String,age: Int = 20){
    print("I am $name.$age years old.")}fun main(a) {
    printParams("erdai")}// Print the result
I am erdai, 20 years old.

// We can also override the default parameters
fun main(a) {
    printParams("erdai".25)}// Print the result
I am erdai, 25 years old.

// Case 2: When we call a function, we can pass parameters in the form key value
fun main(a) {
    // Note that one of the arguments to printParams is name, and the second is age
    printParams(age = 19,name = "erdai666")}// Print the result
I am erdai666, 19 years old.
Copy the code

Tip: We can use the primary constructor instead of the secondary constructor by default

Standard functions and static methods

1, the standard functions let, also, with, run and apply

The let function, which must be called by an object, takes a Lambda expression argument, the argument in the Lambda expression is the current caller, and the last line of code as the return value

2) The also function, which must be called by an object, takes a Lambda expression argument. The Lambda expression argument is the current caller and cannot specify a return value. This function returns the current calling object itself

3) The with function takes two arguments, the first argument of any type, the second argument of a Lambda expression that takes the context of the first argument, this, and the last line of code as the return value

4) The run function, which must be called by an object, takes a Lambda expression argument that takes the context of the currently calling object, this, and returns the last line of code

The apply function, which must be called by an object, takes a Lambda expression argument. The Lambda expression has the context of the object being called, this, and cannot specify a return value. The function returns the object itself

Note: In Lambda expressions, owning the context of the object, this, is the same as owning the object, except that this can be omitted, and owning the object allows us to customize the parameter name. If we do not write the parameter, the it parameter will default

Here’s the code to get a feel for it:

/** * * 1, create a StringBuilder object and call the let function. The argument in the Lambda expression is a StringBuilder object. * 2, omit the Lambda expression when there is only one argument. The return value is the last line of code in the Lambda expression, */
fun main(a) {
    val name = "erdai"
    val age = 20
    val returnValue = StringBuilder().let {
        it.append(name).append("").append(age)
    }
    println(returnValue)
}
// Print the result
erdai 20

/** * Situation 2: Also function * 1, create a StringBuilder object and call the also function. The argument in the Lambda expression is the StringBuilder object * 2, omit the Lambda expression when there is only one argument. By default, there is an it argument. Cannot specify a return value. Returns the calling object itself */
fun main(a) {
    val name = "erdai"
    val age = 20
    val stringBuilder = StringBuilder().also {
        it.append(name).append("").append(age)
    }
    println(stringBuilder.toString())
}
// Print the result
erdai 20

/** * * 1, take two arguments, the first argument is a StringBuilder object, the second argument is a Lambda expression, * 2. The Lambda expression has the context this for the StringBuilder object, and the return value is the last line of code in the Lambda expression */
fun main(a) {
    val name = "erdai"
    val age = 20
    val returnValue = with(StringBuilder()) {
        append(name).append("").append(age)
    }
    println(returnValue)
}
// Print the result
erdai 20

/** * The run function creates a StringBuilder object and calls the also function. The Lambda expression has the context of the StringBuilder object this * 2. The return value is the last line of code in the Lambda expression */
fun main(a) {
    val name = "erdai"
    val age = 20

    val returnValue = StringBuilder().run {
        append(name).append("").append(age)
    }
    println(returnValue)
}
// Print the result
erdai 20

/** * case 5: apply * 1, create a StringBuilder object to call the apply function, the Lambda expression has a context for the StringBuilder object this * 2, cannot specify a return value, return the calling object itself */
fun main(a) {
    val name = "erdai"
    val age = 20

    val stringBuilder = StringBuilder().apply {
        append(name).append("").append(age)
    }
    println(stringBuilder.toString())
}
// Print the result
erdai 20
Copy the code

In fact, the above five standard functions have a lot of similarities, we need to clarify the differences, let’s summarize them with a chart:

Standard functions Function parameters Extension function or not The return value
T.let it is Last line of code
T.also it is The object itself
with this no Last line of code
T.run this is Last line of code
T.apply this is The object itself

2. Define static methods

Kotlin does not directly provide a keyword defined as a static method, but it does provide some similar syntactic features to support the writing of static method calls

1) Use the Companion Object to create a companion class for a class, and then call the companion class’s methods. This method is not called a static method, but can be called as a static method

2) Define a singleton class using the object keyword. Call a method using the singleton class. This method is not called static method, but can be called as static method

If you want to define truly static methods, Kotlin also provides two ways to do so: 1. Use the @jVMStatic annotation, which can only be applied to methods on companion classes and singletons. 2

4) A top-level method is a method that is not defined in any class. A top-level method can be called anywhere. The Kotlin compiler compiles all top-level methods into static methods

If we call a top-level method in Java, by default Java has no concept of a top-level method. The Kotlin compiler generates a Java class where we define this file. For example, I define a top-level method in Kotlin Util. Then a Java class of UtilKt is generated for invocation in Java

6) In Kotlin is more commonly used singletons, associated classes and top-level methods, @jVMstatic annotation is used less

// In Java we can define a static method like this
public class Util {
  
    public static void doAction(){
        System.out.println("do something"); }}There are many static calls like this in Kotlin
// Case 1: Create a companion class for a class using companion Object
fun main(a) {
   Util.doAction()
}

class Util{
    companion object{
        fun doAction(a){
            println("do something")}}}// Print the result
do something

// Case 2: define a singleton class with the object keyword
fun main(a) {
   Util.doAction()
}

object Util {

    fun doAction(a) {
        println("do something")}}// Print the result
do something

// case 3:1, use @jvmstatic annotation 2, define the top-level method
/ / 1
/ / the singleton class
object Util {
  
    @JvmStatic
    fun doAction(a) {
        println("do something")}}/ / associated classes
class Util {
  
    companion object{
        fun doAction(a) {
            println("do something")}}}//2 Use AndroidStudio to create a new File, select File in the popup box, and write a top-level method in the File
// The top-level method can be called anywhere
fun doAction(a){
    println("do something")}Copy the code

You can convert Kotlin files into Java files, and you’ll see the difference between defining true static methods and non-static methods

Delayed initialization and sealing classes

1. Delay initialization of variables

1) Late initialization of a variable using the lateinit keyword

Note when using the lateinit keyword:

1. This function can only be applied to var attributes, and this attribute does not have custom get and set methods

2. This attribute must be of a non-null type and cannot be a native type

2) When you use the lateinit keyword on a variable, the Kotlin compiler will not check to see if the variable is null. Make sure it is initialized before it is called. Otherwise, the program will run with an error. You can use the ::object.isInitialized syntax to determine whether a variable has been initialized

Use lazy to delay initialization of a variable

By lazy:

1, only on the val property

// Case 1: Late initialization of a variable using the lateinit keyword
lateinit var name: String

fun main(a) {
   name = "erdai"
   println(name)
}
// Print the result
erdai

// Case 2: Use the ::object.isInitialized syntax to determine whether the variable has been initialized
lateinit var name: String

fun main(a) {
    if(::name.isInitialized){
        println(name)
    }else{
        println("name not been initialized")}}// Print the result
name not been initialized

// Case 3: Delay initialization of a variable by lazy
// Features: This property is initialized when called, and the Lambda expression after lazy is executed only once
val name: String by lazy {
    "erdai"
}

fun main(a) {
    println(name)
}
// Print the result
erdai
Copy the code

2. Optimize code with sealed classes

Sealed classes allow us to write more standard and secure code

Sealed Class (sealed Class

Sealed classes and their subclasses can only be defined at the top level of the same file

3) Sealed classes can be inherited

4) when we use conditional statements, we need to realize the sealing of all subclasses of the class, to avoid writing code that will never be executed

// We might have written this code before we used sealed classes
interface Result
class Success : Result
class Failure : Result
/** * If we add a new class that implements the Result interface, the compiler will not prompt us to add a new conditional branch * if we do not add a new conditional branch, then we will execute the else * the else is useless, This is simply to satisfy the compiler */
fun getResultMsg(result: Result) = when (result){
    is Success -> "Success"
    is Failure -> "Failure"
    else -> throw RuntimeException()
}

// After using the sealed class
sealed class Result
class Success : Result(a)class Failure : Result(a)/** * We avoid writing the else branch. If I add a new class that implements the Result seal class *, the compiler will prompt when to add the corresponding conditional branch */
fun getResultMsg(result: Result) = when (result){
    is Success -> "Success"
    is Failure -> "Failure"
}
Copy the code

Extension functions and operators

1. Useful extension functions

Extension functions allow us to extend the functions of a class, a feature that is not available in Java

1) The syntax structure of the extension function is as follows:

fun ClassName.methodName(params1: Int, params2: Int) : Int{}Copy the code

In contrast to normal functions, extension functions only need to be preceded by a ClassName. To add the function to the specified class

2) If we want to define an extension function of a class, we will define a Kotlin file with the same name, which is easy to find later. Although we can also define it in any class, but it is recommended to define it as a top-level method, so that the extension method can have global access domain

3) Extension functions have the context of this class by default

For example, if we want to extend the String class with a printString method, we can create a new string. kt file and write the extension function below it:

fun String.printString(a){
    println(this)}fun main(a) {
    val name = "erdai"
    name.printString()
}
// Print the result
erdai
Copy the code

2. Interesting operator overloading

Kotlin’s operator overloading allows us to add any two objects, or perform many more operations

1) Operator overloading uses the operator keyword. We only need to add the operator keyword before the specified function to implement the operator overloading function.

The functions specified above are as follows:

2) For example, I want to implement the function of adding two objects. Its syntax is as follows:

class Obj {
   operator fun plus(obj: Obj): Obj{
     	//do something}}Copy the code

Let’s implement an example of adding money:

class Money(val value: Int) {

    // Implement operator overloading Money + Money
    operator fun plus(money: Money): Money {
        val sum = value + money.value
        return Money(sum)
    }

    // Implement operator overloading Money + Int
    operator fun plus(money: Int): Money{
        val sum = value + money
        return Money(sum)
    }
}

fun main(a) {
    val money1 = Money(15)
    val money2 = Money(20)
    val money3 = money1 + money2
    val money4 = money3 + 15
    println(money3.value)
    print(money4.value)
}

// Print the result
35
50
Copy the code

Nine, higher-order function detailed explanation

Higher-order functions and Lambda expressions go hand in hand. In the previous section, we looked at some of the functional apis, and you saw that they all have one thing in common: they require passing a Lambda expression as an argument. Functions that accept Lambda expressions like this can be called functional programming style apis, and if you want to define your own functional APIS, you need to use higher-order functions to implement them

1. Define higher-order functions

1) Definition of a higher-order function: a function that takes another function as an argument or returns a value is called a higher-order function

Kotlin has a new function type. If we add this function type to a function’s argument declaration or return value, then this is a higher-order function

2) The syntax rules for function types are as follows

(String,Int) - >Unit
// Or the following() - >Unit
Copy the code

The left side of -> declares what type of arguments the function takes, and the right side of -> declares the return value of the function. Now we declare a higher-order function:

fun example(func: (String.Int) - >Unit) {
    //do something
}
Copy the code

3) To call a higher-order function, we just need to add a pair of parentheses after the parameter name and pass in the parameters of the corresponding type. For example, the higher-order function defined above is an example:

fun example(func: (String.Int) - >Unit) {
    // Function type calls
    func("erdai".Awesome!)}Copy the code

Let’s put it into practice:

// We use a higher-order function to get the sum of two numbers
fun numberPlus(num1: Int,num2: Int,func: (Int.Int) - >Int): Int{
    val sum = func(num1,num2)
    return sum
}

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

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

// Two ways to call higher-order functions
// Method 1: member reference. Use ::plus, ::minus to reference a function
fun main(a) {
    val numberPlus = numberPlus(10.20, ::plus)
    val numberMinus = numberPlus(10.20, ::minus)
    println(numberPlus)
    println(numberMinus)
}
// Print the result
30
-10

// Method 2: Use the writing method of the Lambda expression
fun main(a) {
    val numberPlus = numberPlus(10.20){ num1,num2 ->
        num1 + num2
    }
    val numberMinus = numberPlus(10.20){ num1,num2 ->
        num1 - num2
    }
    println(numberPlus)
    println(numberMinus)
}
// Print the result
30
-10
Copy the code

Using Lambda expressions is the most common way to call higher-order functions

2. The role of inline functions

1) Inlining functions eliminates the runtime overhead of Lambda expressions

Kotlin code will eventually be converted to a Java bytecode file, such as 🌰 :

fun numberPlus(num1: Int,num2: Int,func: (Int.Int) - >Int): Int{
    val sum = func(num1,num2)
    return sum
}

fun main(a) {
  	val num1 = 10
  	val num2 = 20
    val numberPlus = numberPlus(num1, num2){ num1,num2 ->
        num1 + num2
    }
}

// The final version of the above code would look something like this:
public static int numberPlus(int num1, int num2, Function operation){
  	int sum = (int) operation.invoke(num1,num2);
  	return sum;
}

public static void main(){
  	int num1 = 10;
  	int num2 = 20;
  	int sum = numberPlus(num1,num2,new Function(){
      	@Override
      	public Integer invoke(Integer num1,Integer num2){
          	returnnum1 + num2; }}); }Copy the code

As you can see, after the transformation, the third argument of the numberPlus Function becomes a Function interface, which is a built-in Kotlin interface with an invoke Function to implement. The numberPlus Function calls the Invoke Function and passes num1 and num2 to it. The previous Lambda expression here becomes an anonymous class implementation of the Function interface. This is the underlying transformation logic of Lambda expressions, so every time we call a Lambda expression, we create a new anonymous class instance, which creates additional memory and performance overhead. But using inline functions, we can solve this problem very well

2) We can call a higher-order function inline by adding the inline keyword

// Define an inline function
inline fun numberPlus(num1: Int,num2: Int,func: (Int.Int) - >Int): Int{
    val sum = func(num1,num2)
    return sum
}
Copy the code

So here’s my question: why does inlining functions eliminate the overhead of running Lambda expressions?

At this point we need to analyze how inline functions work, as follows:

inline fun numberPlus(num1: Int,num2: Int,func: (Int.Int) - >Int): Int{
    val sum = func(num1,num2)
    return sum
}

fun main(a) {
    val num1 = 10
    val num2 = 20
    val numberPlus = numberPlus(num1, num2){ num1,num2 ->
        num1 + num2
    }
}
Copy the code

Step 1: The Kotlin compiler replaces the code in the Lambda expression where the function type argument is called, as shown below:

The replacement code looks like this:

inline fun numberPlus(num1: Int,num2: Int,func: (Int.Int) - >Int): Int{
    val sum = num1 + num2
    return sum
}

fun main(a) {
    val num1 = 10
    val num2 = 20
    val numberPlus = numberPlus(num1, num2);
}
Copy the code

Step 2: The Kotlin compiler replaces all the code in the inline function to the place where the function is called, as shown below:

The replacement code looks like this:

fun main(a) {
    val num1 = 10
    val num2 = 20
    val numberPlus = num1 + num2
}
Copy the code

This step is a workflow for inlining functions: the Kotlin compiler automatically replaces the code in the inlining function at compile time where it is called, so there is no runtime overhead

3) A function type argument that uses the noinline keyword to indicate that the function type argument does not need to be inlined

The noinline keyword is usually used when there are arguments of multiple function types in an inline function

// If a higher-order function is defined using an inline function, its type arguments will be inlined, so noinline means that the type arguments of the function do not need to be inlined
inline fun inlineTest(block1: () -> Unit.noinline block2: () -> Unit){}Copy the code

We talked earlier about how inlining functions can reduce runtime overhead. Why do we use the noinline keyword to define that inlining is not required? Here’s why:

1. Inline functions are compiled with code substitution, so they have no real argument properties. Their function type arguments can only be passed to another inline function, whereas non-inline functions can pass function type arguments freely to any other function

Lambda expressions referenced by inline functions can be returned using the return keyword. Lambda expressions referenced by non-inline functions can be locally returned using the return@Method syntactic structure

// Case 1: A Lambda expression referenced by a non-inline function can be locally returned using the return keyword
// Define a noninlined higher-order function
fun printString(str: String, block: (String) - >Unit){
    println("printString start...")
    block(str)
    println("printString end...")}fun main(a) {
    println("main start...")
    val str = ""
    printString(str){
        println("lambda start...")
      	/** * 1, non-inlined functions can not use the return keyword directly to return * 2, need to use return@printStringPerform a local return */
        if (str.isEmpty())return@printString
        println(it)
        println("lambda end...")
    }
    println("main end...")}// Print the result
main start...
printString start...
lambda start...
printString end...
main end...

// Case 2: Inline functions refer to Lambda expressions that can be returned using the return keyword
// Define a noninlined higher-order function
inline fun printString(str: String, block: (String) - >Unit){
    println("printString start...")
    block(str)
    println("printString end...")}fun main(a) {
    println("main start...")
    val str = ""
    printString(str){
        println("lambda start...")
        if (str.isEmpty())return
        println(it)
        println("lambda end...")
    }
    println("main end...")}// Since inline functions do code substitution, this return is equivalent to the return of an outer function call, as follows:
fun main(a) {
    println("main start...")
    val str = ""
    println("printString start...")
    println("lambda start...")
    if (str.isEmpty())return
    println(str)
    println("lambda end...")
    println("printString end...")
    println("main end...")}// Print the result
main start...
printString start...
lambda start...
Copy the code

4) Use the crossinline keyword to ensure that inline functions do not use the return keyword in their Lambda expressions. However, you can still use the return@Method syntax structure for local returns, otherwise consistent with inline functions

Take 🌰 using the Crossinline scenario:

The code in the above image is in error. The compiler tells us roughly because this place cannot be inline because it may contain a non-local return. Add the crossinline modifier to the parameters of this function type.

Why? Let’s break it down:

We create a Runnable object and call the function type parameter in the Runnable Lambda expression, which is converted to an anonymous inner class at compile time. Inline functions allow us to use the return keyword in a Lambda expression, but since we are calling the function type argument in an anonymous class, it is not possible to call the outer function return, at most in an anonymous function return, so this error is displayed. So let’s use the crossinline keyword to change that

inline fun runRunnable(crossinline block: () -> Unit) {
    println("runRunnable start...")
    val runnable = Runnable {
        block()
    }
    runnable.run()
    println("runRunnable end...")}fun main(a) {
    println("main start...")
    runRunnable {
        println("lambda start...")
        return@runRunnable
        println("lambda end...")
    }
    println("main end...")}// Print the result
main start...
runRunnable start...
lambda start...
runRunnable end...
main end...
Copy the code

Generics and delegates

1. Basic usage of generics

Generics are parameterized types that allow us to program without specifying a specific type. When we define a class, method, or interface and add a type parameter to it, we add a generic type to the class, method, or interface

//1, define a generic class, using the syntax structure 
      
        after the class name to define a generic class
      
class MyClass<T>{
  	fun method(params: T){}}// Generic calls
val myClass = MyClass<Int>()
myClass.method(12)

//2, define a generic method by adding 
      
        before the method name
      
class MyClass{
    fun <T> method(params: T){}}// Generic calls
val myClass = MyClass()
myClass.method<Int> (12)
// We can omit generics according to the Kotlin type derivation mechanism
myClass.method(12)

//3. Define a generic interface by adding 
      
        to the interface name
      
interface MyInterface<T>{
    fun interfaceMethod(params: T)
}
Copy the code

The T above is not fixed and can be any word or letter, but the generics defined are as generic as possible


type

class MyClass{
  	// We specify the upper bound of the generic type as Number, so we can only pass in numeric parameters
  	fun <T : Number> method(params: T){}}Copy the code

2. Class delegate and delegate properties

The meaning of the delegate pattern is that most of our method implementations can call auxiliary objects to implement, and a few of our method implementations can be overwritten by themselves, or even add some of their own methods, so that our class becomes a new data structure class

1) The core idea of class delegation is to delegate the concrete implementation of one class to another class, using the by keyword for delegation

// Define a class MySet whose implementation delegates to a class called HashSet
class MySet<T>(val helperSet: HashSet<T>) : Set<T>{

    override val size: Int get() = helperSet.size

    override fun contains(element: T) = helperSet.contains(element)

    override fun containsAll(elements: Collection<T>) = helperSet.containsAll(elements)

    override fun isEmpty(a) = helperSet.isEmpty()

    override fun iterator(a) = helperSet.iterator()
}

/** * If we use the by keyword, the above code will be very clean, and we can override or add methods * then MySet will become a new data structure class */
class MySet<T>(val helperSet: HashSet<T>) : Set<T> by helperSet{
     fun helloWord(a){
        println("Hello World")}override fun isEmpty(a) = false
}
Copy the code

2) The core idea of attribute delegation is to delegate the implementation of a property to another class

The syntax of attribute delegation is as follows:

/** * connect the p attribute on the left to the Delegate instance on the right using the by keyword
class MyClass{

   var p by Delegate()
}

/** * the getValue method must be modified with the operator keyword. ** The getValue method takes two main arguments: The second argument, KProperty<*>, is a property action class in Kotlin. * can be used to get values related to various properties. <*> This generic type is written like Java * 
      
class Delegate{
   
   var propValue: Any? = null

   operator fun getValue(any: Any? ,prop:KProperty< * >): Any? {return propValue
   }

   operator fun setValue(any: Any? ,prop:KProperty<*>,value: Any?).{
       propValue = value
   }
}
Copy the code

Use the infix function to build more readable syntax

The infix function syntax is readable and is closer to using English A to B syntax than calling A function

For example, we can call A function using the structure: A.to(B), but with infix we can write: A to B, which is the syntax we used when we talked about Map

// Define an immutable set of maps
val map1 = mapOf("Apple" to 1."Banana" to 2."Orange" to 3."Pear" to 4."Grape" to 5)
Copy the code

1) Declare an infix function by prefixing the function with the infix keyword

// Add an extended infix function to String, and end up calling String startsWith
infix fun String.beginWith(string: String) = startsWith(string)

fun main(a) {
    val name = "erdai"
    println(name beginWith "er")}// Print the result
true
Copy the code

Let’s initialize A Map with an infix function called A to B

// This is the source code implementation of A to B
public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

// Let's write one after it
public infix fun <A,B> A.with(that: B): Pair<A,B> = Pair(this,that)

fun main(a) {
    val map = mapOf("Apple" with 1."Banana" with 2."Orange" with 3."Pear" with 4."Grape" with 5)}Copy the code

Use DSLS to build proprietary syntactic structures

1) Introduction to DSL

DSL English full name: Domain Specific Language, Chinese translation is a domain specific language, such as: HTML, XML and other DSL languages

The characteristics of

  • Solve problems that are specific to a particular domain
  • It and the system programming language is to go to two extremes, the system programming language is to hope to solve all the problems, such as Java language hope to do Android development, and hope to do background development, it has the horizontal expansion of the characteristics. DSLS, on the other hand, have the ability to drill down vertically to solve domain-specific problems.

In general, the core idea of DSLS is to “solve specific problems rather than complete problems”.

2) Kotin DSL

Let’s start with Gradle: Gradle is an open source automated build tool, a Groovy or Kotin-based DSL. Our Android applications are built using Gradle, so we can use Kotlin to write scripts and plug-ins, and AndroidStudio support for Kotlin is very friendly, all kinds of tips, writing is very cool.

For our Android development, the common way to add dependencies to build.gradle files is:

dependencies {
    implementation 'androidx. Core: the core - KTX: 1.3.2'
    implementation 'androidx. Appcompat: appcompat: 1.2.0'
}
Copy the code

This is a Groovy-based DSL. Let’s use Kotlin to implement a similar DSL:

class Dependency {

    fun implementation(lib: String){}}fun dependencies(block: Dependency. () - >Unit){
    val dependency = Dependency()
    dependency.block()
}

fun main(a) {
    // The syntax of Groovy and Kotlin is different, so the writing is a little different
    dependencies {
        implementation ("Androidx. Core: the core - KTX: 1.3.2." ")
        implementation ("Androidx. Appcompat: appcompat: 1.2.0")}}Copy the code

Conversion between Java and Kotlin code

Java code to Kotlin code

There are two ways:

1) copy Java code directly into a Kotlin file and AndroidStudio will prompt you to convert

2) Open the Java File you want to Convert and click Code -> Convert Java File to Kotlin File in the navigation bar

Kotlin code to Java code

Open the Kotlin file that needs to be converted. In the navigation bar, click Tools -> Kotlin ->Show Kotlin Bytecode. The following interface will appear:

Click Decompile to Decompile the Kotlin bytecode file into Java code

Xiv. Summary

This article is very long, and we have introduced most of the knowledge points of Kotlin. According to the mind map at the beginning of the article, we only have Kotlin generics advanced features and Kotlin Ctrip. These two parts are relatively difficult, so we will analyze them in detail later. Believe that if you see here from the beginning, harvest must be a lot, if think I wrote good, please give me a like 🤝

References and Recommendations

[the first line of code Android version 3] : Guo God produced, must be a boutique, Kotlin explained to write easy to understand

Here is the full text, the original is not easy, welcome to like, collect, comment and forward, your recognition is the motivation of my creation