Introduction to Kotlin (full)

1. Hello World

According to international practice, learning a new language usually starts with printing Hello World

package main

fun main() {
    
    
    val msg: String = "Hello World"
    println(msg)
}

From this simple function, several differences between kotlin and Java can be listed

  1. Functions can be defined at the outermost level of the file without putting it in the class
  2. Use the keyword fun to declare a function
  3. mainParameters of the method can be omitted
  4. The parameter type is written after the variable name, which helps to omit the type declaration when the type is automatically deduced
  5. Use printlnInstead System.out.println, this is a simple wrapper for Java standard library functions provided by the kotlin standard library
  6. The semicolon at the end of the code can be omitted

2. Package

All kotlin files start with package , and all declarations (classes, functions, and properties) defined in the same file will be placed in this package. Declarations in the same package can be used directly, and declarations in different packages need to be imported and used

Package declarations are placed at the top of the file, followed by import statements

package base

import java.text.SimpleDateFormat
import java.util.*

Kotlin does not distinguish whether the import is a class or a function, you can use importthe keyword to import any kind of declaration. In addition, you can add . * after the package name to import all declarations defined in the package, so that the classes, top-level functions, and properties defined in the package can be directly referenced

package learn.package2

val index = 10

fun Test(status: Boolean) = println(status)

class Point(val x: Int, val y: Int) {
    
    

    val isEquals1: Boolean
        get() {
    
    
            return x == y
        }

    val isEquals2
        get() = x == y

    var isEquals3 = false
        get() = x > y
        set(value) {
    
    
            field = !value
        }

}
package learn.package1

import learn.package2.Point
import learn.package2.Test
import learn.package2.index

fun main() {
    
    
    val point = Point(10, index)
    Test(true)
}

The Java language stipulates that classes should be placed in a folder directory structure that matches the package structure, and the file name must be the same as the class name. Kotlin allows multiple classes to be placed in the same file, and the file name can also be chosen arbitrarily. Kotlin does not impose any restrictions on the folder directory, and the package hierarchy does not need to follow the same directory hierarchy, but kotlin officials still recommend that the source code files be placed in the corresponding directory according to the package declaration

If there is a naming conflict in the package name, you can use the as keyword to rename the conflicting item locally to disambiguate

import learn.package1.Point
import learn.package2.Point as PointTemp //PointTemp 可以用来表示 learn.package2.Point 了

There is also a similar concept in kotlin that can be used to rename existing types, called type aliases. Type aliases are used to provide an alternative name for an existing type. If the type name is relatively long, it can be easier to remember by introducing a shorter or more concise name.

Type aliases do not introduce new types, and still correspond to their underlying types, so the class types output in the following code are consistent

class Base {
    
    

    class InnerClass

}

typealias BaseInner = Base.InnerClass

fun main() {
    
    
    val baseInner1 = Base.InnerClass()
    val baseInner2 = BaseInner()
    println(baseInner1.javaClass) //class test2.Base$InnerClass
    println(baseInner2.javaClass) //class test2.Base$InnerClass
}

3. Variables and data types

In Java, most variables are mutable (non-final), meaning that any code that has access to the variable can modify it. In kotlin, variables can be divided into two types : variable variables (var) and immutable variables (val).

There are two keywords for declaring variables:

  • val(value / varible+final) – immutable reference. Variables declared using val cannot be reassigned after initialization, corresponding to final variables in Java
  • var (variable) - variable reference. The value of the var variable can be changed, corresponding to the non-final variable in Java

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

The programming field also advocates a development principle: use val, immutable objects, and pure functions to design programs as much as possible. In this way, the influence of side effects can be avoided as much as possible

fun main() {
    
    
    //只读变量即赋值后不可以改变值的变量,用 val 声明
    //声明一个整数类型的不可变变量
    val intValue: Int = 100

    //声明一个双精度类型的可变变量
    var doubleValue: Double = 100.0
}

When declaring variables, we usually do not need to explicitly specify the type of the variable, which can be automatically deduced by the compiler based on the context. If the read-only variable has no initial default value at the time of declaration, the variable type must be specified, and the variable must be initialized under each branch condition before use, otherwise the compiler will report an exception

fun main() {
    
    
    val intValue = 1002222222222
    val doubleValue = 100.0
    val longValue = 100L

    //如果只读变量在声明时没有初始值,则必须指明变量类型
    val intValue2: Int
    if (false) {
    
    
        intValue2 = 10
    }
    println(intValue2) //error, Variable 'intValue2' must be initialized
}

1. Basic data types

Unlike Java, kotlin does not distinguish between primitive data types and its wrapper classes, everything in kotlin is an object, and its member functions and properties can be called on any variable. Kotlin does not have the original basic types like in Java, but types such as byte, char, integer, float or boolean are still reserved, but all exist as objects

For basic types, kotlin has several special features compared to Java

  • Numbers, characters, and booleans can be represented as primitive values ​​at runtime, but to the developer they look like normal classes
  • There is no implicit widening conversion for numbers , whereas int can be implicitly converted to long in Java
  • All variables initialized with integer values ​​that do not exceed the maximum value of Int will be automatically deduced as Int type. If the initial value exceeds its maximum value, it will be deduced as Long type. If you need to explicitly specify the value of Long type, you can set it in the value Append the L suffix
  • character cannot be considered a number
  • Octal not supported
//在 kotlin 中,int、long、float 等类型仍然存在,但是是作为对象存在的

val intIndex: Int = 100
//等价于,编译器会自动进行类型推导
val intIndex = 100

//数字类型不会自动转型,必须要进行明确的类型转换
val doubleIndex: Double = intIndex.toDouble()
//以下代码会提示错误,需要进行明确的类型转换
//val doubleIndex: Double = intIndex

val intValue: Int = 1
val longValue: Long = 1
//以下代码会提示错误,因为两者的数据类型不一致,需要转换为同一类型后才能进行比较
//println(intValue == longValue)

//Char 不能直接作为数字来处理,需要主动转换
val ch: Char = 'c'
val charValue: Int = ch.toInt()
//以下代码会提示错误
//val charValue: Int = ch

//二进制
val value1 = 0b00101
//十六进制
val value2 = 0x123

Also, kotlin's nullable types cannot be represented by Java's primitive data types, because null can only be stored in variables of Java's reference type, which means that as long as the nullable version of the primitive data type is used, it will be compiled into the corresponding packaging type

//基本数据类型
val intValue_1: Int = 200
//包装类
val intValue_2: Int? = intValue_1
val intValue_3: Int? = intValue_1
//== 比较的是数值相等性,因此结果是 true
println(intValue_2 == intValue_3)
//=== 比较的是引用是否相等,因此结果是 false
println(intValue_2 === intValue_3)

If the value of intValue_1 is 100, you will find that the comparison result of intValue_2 === intValue_3 is true, which involves Java's reuse of wrapper objects

2. String

Like Java, kotlin uses the String type to represent strings. Strings are immutable. You can use index operators []to access individual characters contained in them, or you can use for loops to iterate strings. You can also use + to connect characters string

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

kotlin supports local variables referenced in string literals, you only need to add the character $ before the variable name, and you can also include expressions enclosed in curly braces, which will be automatically evaluated and the results will be merged into the string middle

val intValue = 100
//可以直接包含变量
println("intValue value is $intValue") //intValue value is 100
//也可以包含表达式
println("(intValue + 100) value is ${
      
      intValue + 100}")   //(intValue + 100) value is 200

If you need to represent literal ($) characters (which do not support backslash escapes) in raw strings, use the following syntax:

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

3. Array

An array in kotlin is a class with type parameters whose element type is specified as the corresponding type parameter, represented by the Array class, which defines the get and set functions (according to the operator overloading convention this will be converted to []) and size attribute etc.

There are several ways to create an array:

  1. Use the arrayOf function to create an array whose elements are specified as arguments to the function
  2. Use arrayOfNulls to create an array of a given size, all elements contained are null, and can only be used to create an array containing element types that can be null
  3. Call the constructor of the Array class, pass the size of the array and a lambda expression, and call the lambda expression to create each array element
//包含给定元素的字符串数组
val array1 = arrayOf("leavesCZY", "叶", "https://github.com/leavesCZY")

array1[0] = "leavesCZY"
println(array1[1])
println(array1.size)

//初始元素均为 null ,大小为 10 的字符数组
val array2 = arrayOfNulls<String>(10)

//创建从 “a” 到 “z” 的字符串数组
val array3 = Array(26) {
    
     i -> ('a' + i).toString() }

It is important to note that a type parameter of an array type always becomes an object type, so declaring Array<Int> will be an array containing a boxed type (java.lang.Integer). If you want to create arrays of primitive types without boxing, you must use a special class for arrays of primitive types

In order to represent arrays of basic data types, kotlin provides several corresponding classes for each basic data type and makes special optimizations. For example, there are types such as IntArray, ByteArray, BooleanArray , etc. These types will be compiled into arrays of common Java basic data types, such as int[], byte[], boolean[], etc., and the values ​​in these arrays are not boxed. , but the most efficient way possible. It should be noted that IntArray etc. are not subclasses of Array

To create an array of primitive data types, there are several ways:

  1. Pass the array size to the constructor of the corresponding type of class (such as IntArray), which will return an array initialized with the default value of the corresponding basic data type
  2. Pass the array size and the lambda used to initialize each element to the constructor of the corresponding type of class (such as IntArray)
  3. Pass the value of the variable length parameter to the factory function (such as charArrayOf) to get an array of specified element values
//指定数组大小,包含的元素将是对应基本数据类型的默认值(int 的默认值是 0)
val intArray = IntArray(5)
//指定数组大小以及用于初始化每个元素的 lambda
val doubleArray = DoubleArray(5) {
    
     Random().nextDouble() }
//接收变长参数的值来创建存储这些值的数组
val charArray = charArrayOf('H', 'e', 'l', 'l', 'o')

4. Any and Any?

The Any type is the supertype of all non-nullable types in kotlin, including basic data types like Int

If the value of the basic data type is assigned to a variable of type Any, it will be automatically boxed

val any: Any = 100
println(any.javaClass) //class java.lang.Integer

If you want a variable to store all possible values ​​including null, you need to use Any?

val any: Any? = null

5、Unit

The Unit type in kotlin is similar to void in Java and can be used when the function has no return value

fun check(): Unit {
    
    

}

//如果返回值为 Unit,则可以省略该声明
fun check() {
    
    

}

Unit is a complete type and can be used as a type parameter, but void cannot

interface Test<T> {
    
    
    fun test(): T
}

class NoResultClass : Test<Unit> {
    
    
    
    //返回 Unit,但可以省略类型说明,函数也不需要显式地 return 
    override fun test() {
    
    

    }

}

6、Nothing

The Nothing type has no value, and it only makes sense when it is used as a function return value, or as a type parameter of a generic function return value. You can use Nothing to indicate that a function will not be terminated normally, thereby helping the compiler to understand code for diagnosis

The compiler knows that a function with a return value of type Nothing will never terminate normally, so the compiler will infer the type of name1 to be non-null, because branch handling when name1 is null will always throw an exception

data class User(val name: String?)

fun fail(message: String): Nothing {
    
    
    throw IllegalStateException(message)
}

fun main() {
    
    
    val user = User("leavesC")
    val name = user.name ?: fail("no name")
    println(name) //leavesC

    val user1 = User(null)
    val name1 = user1.name ?: fail("no name")
    println(name1.length) //IllegalStateException
}

4. Function

A function in kotlin begins with the keyword fun, followed by the function name, followed by a parameter list wrapped in parentheses. If the function has a return value, then the return value type is added, separated from the parameter list by a colon open

//fun 用于表示声明一个函数,getNameLastChar 是函数名
//空括号表示该函数无传入参数,Char 表示函数的返回值类型是字符
fun getNameLastChar(): Char {
    
    
    return name.get(name.length - 1)
}

//带有两个不同类型的参数,一个是 String 类型,一个是 Int 类型
//返回值为 Int 类型
fun test1(str: String, int: Int): Int {
    
    
    return str.length + int
}

In addition, the return value type of the expression function body can be omitted, and the return value type can be automatically inferred. This kind of function defined with a single-line expression and an equal sign is called an expression function body . But for the code block function body with return value under normal circumstances , the return type and return statement must be written explicitly

//getNameLastChar 函数的返回值类型以及 return 关键字是可以省略的
//返回值类型可以由编译器根据上下文进行推导
//因此,函数可以简写为以下形式
fun getNameLastChar() = name.get(name.length - 1)

If the function has no meaningful return value, it can be declared as Unit, or Unit can be omitted

The following three ways of writing are equivalent

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

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

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

1. Named parameters

In order to enhance the readability of the code, kotlin allows us to use named parameters, that is, when calling a function, the function parameter name can be marked together, so as to clearly express the meaning and function of the parameter, but when specifying a parameter name After that, all subsequent parameters need to be marked with a name

fun main() {
    
    
    //错误,在指定了一个参数的名称后,之后的所有参数都需要标明名称
    //compute(index = 110, "leavesC")
    compute(index = 120, value = "leavesC")
    compute(130, value = "leavesC")
}

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

}

2. Default parameter values

You can specify the default value of the parameter when declaring the function, so as to avoid creating an overloaded function

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

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

}

For the above example, if the conventional call syntax is used, the parameters must be given in the order of the parameters defined by the function declaration, and only the parameters at the end can be omitted

fun main() {
    
    
    //错误,不能省略参数 name
    // compute(24)
    // compute(24,100)
    //可以省略参数 value
    compute("leavesC", 24)
}

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

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

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

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

}

3. Variable parameters

Variable parameters allow us to pack any number of parameters into an array and pass them to the function. Kotlin's syntax is different from that of Java. Instead, use the vararg keyword to declare variable parameters.

For example, the following function calls are all correct

fun main() {
    
    
    compute()
    compute("leavesC")
    compute("leavesC", "leavesc")
    compute("leavesC", "leavesc", "叶")
}

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

In Java, arrays can be passed directly to variable parameters, while kotlin requires explicit unpacking of arrays so that each array element can be called as a separate parameter in the function. This feature is called the spread operator, using The way is to add a * before the array parameter

fun main() {
    
    
    val names = arrayOf("leavesC", "leavesc", "叶")
    compute(* names)
}

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

4. Local functions

Kotlin supports nesting functions in functions, and the nested functions are called local functions

fun main() {
    
    
    compute("leavesC", "country")
}

fun compute(name: String, country: String) {
    
    
    fun check(string: String) {
    
    
        if (string.isEmpty()) {
    
    
            throw IllegalArgumentException("参数错误")
        }
    }
    check(name)
    check(country)
}

5. Expressions and conditional loops

1. Statements and expressions

Here we need to distinguish between the two concepts of "statement" and "expression". A statement is a code that can be executed independently and can produce actual effects. It is expressed in the form of assignment logic, printing operation, flow control, etc. Flow control (if, while, for) in Java are all statements. An expression can be a value, a variable, a constant, an operator, or a combination of them. An expression can be regarded as a statement that contains a return value

For example, the following assignment operations, flow control, and printouts are all statements, which exist as a whole and do not contain return values

val a = 10
for (i in 0..a step 2) {
    
    
    println(i)
}

Look at a few more examples of expressions

1       //字面表达式,返回 1

++1     //也属于表达式,自增会返回 2

//与 Java 不同,kotlin 中的 if 是作为表达式存在的,其可以拥有返回值
fun getLength(str: String?): Int {
    
    
    return if (str.isNullOrBlank()) 0 else str.length
}

2. If expression

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

val maxValue = if (20 > 10) {
    
    
    println("maxValue is 20")
    20
} else {
    
    
    println("maxValue is 10")
    10
}
println(maxValue) //20

The following code can clearly see that the return value of if can be used to replace the ternary operator in Java , so kotlin does not have a ternary operator

val list = listOf(1, 4, 10, 4, 10, 30)
val value = if (list.size > 0) list.size else null
println(value)  //6

If the if expression branch is used to execute a certain command, then the return value type at this time is Unit, and the if statement at this time looks the same as Java

val value1 = if (list.size > 0) println("1") else println("2")
println(value1.javaClass)   //class kotlin.Unit

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

3. when expression

when expressions are similar to switch/case in Java , but much more powerful. when can be used either as an expression or as a statement, when compares the parameters with all branch conditions in order until one branch meets the condition, then it will run the expression on the right. If when is used as an expression, the value of the qualified branch is the value of the entire expression, and if it is used as a statement, the value of individual branches is ignored. The difference from Java's switch/case is that the parameter of the when expression can be of any type, and the branch can also be a condition

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

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

val value = 2
when (value) {
    
    
    in 4..9 -> println("in 4..9") //区间判断
    3 -> println("value is 3")    //相等性判断
    2, 6 -> println("value is 2 or 6")    //多值相等性判断
    is Int -> println("is Int")   //类型判断
    else -> println("else")       //如果以上条件都不满足,则执行 else
}
fun main() {
    
    
    //返回 when 表达式
    fun parser(obj: Any): String =
            when (obj) {
    
    
                1 -> "value is 1"
                "4" -> "value is string 4"
                is Long -> "value type is long"
                else -> "unknown"
            }
    println(parser(1))
    println(parser(1L))
    println(parser("4"))
    println(parser(100L))
    println(parser(100.0))
}

value is 1
value type is long
value is string 4
value type is long
unknown

In addition, the when statement can also be used without parameters

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

4. for loop

The most similar form of for loop in Java is

val list = listOf(1, 4, 10, 34, 10)
for (value in list) {
    
    
    println(value)
}

traverse by index

val items = listOf("H", "e", "l", "l", "o")
//通过索引来遍历List
for (index in items.indices) {
    
    
    println("${
      
      index}对应的值是:${
      
      items[index]}")
}

It is also possible to get the current index and corresponding value in each loop

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

You can also customize the cycle interval

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

5. while and do/while loops

while and do/while are not much different from Java

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

6. Back and jump

Kotlin has three structured jump expressions:

  • return defaults to returning from the most immediately enclosing function or anonymous function
  • break terminates the loop that immediately encloses it
  • continue continues to the next most immediately enclosing loop

Any expression in kotlin can be marked with a label. The format of the label is an identifier followed by the @ symbol. For example: abc@, fooBar@ are valid labels

fun main() {
    
    
    fun1()
}

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

kotlin has function literals, local functions and object expressions. So kotlin functions can be nested

The tag-restricted return allows us to return from the outer function, and one of the most important uses is to return from a lambda expression. It is often more convenient to use an implicit label with the same name as the function that accepts the lambda

fun main() {
    
    
    fun1()
    fun2()
    fun3()
    fun4()
    fun5()
}

fun fun1() {
    
    
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach {
    
    
        if (it == 8) {
    
    
            return
        }
        println("value is $it")
    }
    println("function end")

//    value is 1
//    value is 4
//    value is 6
}

fun fun2() {
    
    
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach {
    
    
        if (it == 8) {
    
    
            return@fun2
        }
        println("value is $it")
    }
    println("function end")

//    value is 1
//    value is 4
//    value is 6
}

//fun3() 和 fun4() 中使用的局部返回类似于在常规循环中使用 continue
fun fun3() {
    
    
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach {
    
    
        if (it == 8) {
    
    
            return@forEach
        }
        println("value is $it")
    }
    println("function end")
    
//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun4() {
    
    
    val list = listOf(1, 4, 6, 8, 12, 23, 40)
    list.forEach loop@{
    
    
        if (it == 8) {
    
    
            return@loop
        }
        println("value is $it")
    }
    println("function end")

//    value is 1
//    value is 4
//    value is 6
//    value is 12
//    value is 23
//    value is 40
//    function end
}

fun fun5() {
    
    
    listOf(1, 2, 3, 4, 5).forEach(fun(value: Int) {
    
    
        if (value == 3) {
    
    
            //局部返回到匿名函数的调用者,即 forEach 循环
            return
        }
        println("value is $value")
    })
    println("function end")
}

6. Interval

Ranges expressions use a ... operator to declare a closed range, which is used to define the implementation of a RangTo method

The following three declarations are equivalent

var index = 5

if (index >= 0 && index <= 10) {
    
    

}

if (index in 0..10) {
    
    

}

if (index in 0.rangeTo(10)) {
    
    

}

When ranges of numeric types are iterated, the compiler will convert them into the same bytecode as the for loop using index in Java for optimization

Ranges are self-increasing by default, so code like the following will not be executed

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

You can use the downTo function to change it to decrease

for (index in 10 downTo 0) {
    
    
    println(index)
}

You can use step in ranges to define the length that is incremented or incremented each time through the loop:

for (index in 1..8 step 2){
    
    
    println(index)
}
for (index in 8 downTo 1 step 2) {
    
    
    println(index)
}

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

for (index in 0 until 4){
    
    
    println(index)
}

The extension function reversed()can be used to return the sequence after the interval is reversed

val rangeTo = 1.rangeTo(3)
for (i in rangeTo) {
    
    
    println(i) //1  2  3
}
for (i in rangeTo.reversed()) {
    
    
    println(i) //3  2  1
}

Seven, modifiers

1. final and open

Classes and methods in kotlin are final by default, that is, non-inheritable. If you want to allow the creation of a subclass of a class, you need to use the open modifier to identify this class. In addition, you also need to specify for each attribute that you want to be overridden and method add open modifier

open class View {
    
    
    open fun click() {
    
    

    }
	//不能在子类中被重写
    fun longClick() {
    
    

    }
}

class Button : View() {
    
    
    override fun click() {
    
    
        super.click()
    }
}

If a member of a base class or interface is overridden, the overridden member is also open by default. For example, if the Button class is open, its subclasses can also override its click() method

open class Button : View() {
    
    
    override fun click() {
    
    
        super.click()
    }
}

class CompatButton : Button() {
    
    
    override fun click() {
    
    
        super.click()
    }
}

If you want subclasses of your class to override the implementation of this method, you can explicitly mark the overridden member as final

open class Button : View() {
    
    
    override final fun click() {
    
    
        super.click()
    }
}

2、public

The public modifier is the least restrictive modifier and is the default modifier. If a member defined as public is contained in a private modified class, then this member is not visible outside this class

3、protected

The protected modifier can only be used on members of classes or interfaces. In Java, a protected member can be accessed from the same package, but for kotlin, protected members are only visible in that class and its subclasses. Also, protected does not apply to top-level declarations

4、internal

A package member defined as internal is visible to the entire module it is in, but invisible to other modules. For example, suppose we want to publish an open source library, which contains a certain class. We want this class to be globally visible to the library itself, but it cannot be referenced by external users. At this time, we can choose to make it Declared as internal to achieve this purpose

According to the definition of Jetbrains, a module should be a single functional unit, which can be regarded as a collection of kotlin files compiled together, and it should be able to be compiled, run, tested, and debugged separately. It is equivalent to the module referenced by the main project in Android Studio, and different projects in a workspace in Eclipse

5、private

The private modifier is the most restrictive modifier. Kotlin allows the use of private visibility in top-level declarations, including classes, functions, and properties, which means that it is only visible in the file where it is located, so if you declare a class as private, you can It cannot be used outside of where this class is defined. In addition, if the private modifier is used in a class, the access rights are limited to this class, and subclasses that inherit this class cannot use it. So if classes, objects, interfaces, etc. are defined as private, they are only visible to the file where they are defined. If defined in a class or interface, they are only visible to that class or interface

6. Summary

Modifier class members top-level declaration
public (default) visible everywhere visible everywhere
internal visible in the module visible in the module
protected visible in subclasses
private visible in class visible in the file

8. Air safety

1. Nullability

In kotlin, the type system divides a reference into two types that can hold null (null reference) or cannot hold null (non-null reference). For example, a regular variable of type String cannot point to null

var name: String = "leavesC"
//编译错误
//name = null

If you want a variable to store a null reference, you need to explicitly add a question mark after the type name

var name: String? = "leavesC"
name = null

A question mark can be added after any type to indicate that a variable of this type can store a null reference: Int?、Doubld? 、Long?etc.

Kotlin's explicit support for nullable types helps prevent exceptions caused by NullPointerException . The compiler does not allow direct calls to nullable variables without checking their properties for null. This mandatory regulation makes developers have to consider the assignable range of variables at the early stage of coding and do branching for each situation

fun check(name: String?): Boolean {
    
    
    //error,编译器不允许不对 name 做 null 检查就直接调用其属性
    return name.isNotEmpty()
}

The correct approach is to explicitly check for null

fun check(name: String?): Boolean {
    
    
    if (name != null) {
    
    
        return name.isNotEmpty()
    }
    return false
}

2. Safe call operator: ?.

Safe call operator: ?.Allows to combine a null check and a method call into one operation. If the variable value is not empty, the method or property will be called, otherwise it will return null directly

For example, the following two ways of writing are completely equivalent:

fun check(name: String?) {
    
    
    if (name != null) {
    
    
        println(name.toUpperCase())
    } else {
    
    
        println(null)
    }
}

fun check(name: String?) {
    
    
    println(name?.toUpperCase())
}

3. Elvis operator: ?:

Elvis operator: ?:It is used to replace ?.the case of directly returning the default value null. The Elvis operator receives two operands. If the first operand is not null, the operation result is its operation result value. If the first operand is null , the result of the operation is the second operand

For example, the following two ways of writing are completely equivalent:

fun check(name: String?) {
    
    
    if (name != null) {
    
    
        println(name)
    } else {
    
    
        println("default")
    }
}

fun check(name: String?) {
    
    
    println(name ?: "default")
}

4. Safe conversion operator: as?

Safe conversion operator: as?used to convert the value to the specified type, and return null if the value is not suitable for the type

fun check(any: Any?) {
    
    
    val result = any as? String
    println(result ?: println("is not String"))
}

5. Non-null assertion:!!

Non-null assertion is used to convert any value to a non-null type. If a non-null assertion is made on a null value, an exception will be thrown

fun main() {
    
    
    var name: String? = "leavesC"
    check(name) //7

    name = null
    check(name) //kotlinNullPointerException
}

fun check(name: String?) {
    
    
    println(name!!.length)
}

6. Expansion of Nullable Types

Defining an extension function for a nullable type is a more powerful way to handle null values, allowing the caller to be null, and handling null in that function instead of calling its method after making sure the variable is not null

For example, the following method can be called normally without a null pointer exception

val name: String? = null
println(name.isNullOrEmpty()) //true

isNullOrEmpty()The method signature is as follows, you can see that this is an extension function defined for the nullable type CharSequence?, and the method has already handled the case that the method caller is null

@kotlin.internal.InlineOnly
public inline fun CharSequence?.isNullOrEmpty(): Boolean {
    
    
    contract {
    
    
        returns(false) implies (this@isNullOrEmpty != null)
    }
    return this == null || this.length == 0
}

7. Platform type

The platform type is a balanced design made by kotlin for java. Kotlin divides object types into nullable and non-nullable types, but all object types on the java platform are nullable. When referencing java variables in kotlin, if all variables are classified as nullable types, You'll end up with a lot of extra null checks; if you treat them all as non-nullable types, it's easy to write code that ignores the risk of NPEs. In order to balance the two, kotlin introduces platform types, that is, when referencing java variable values ​​in kotlin, it can be regarded as either a nullable type or a non-nullable type, and it is up to the developer to decide whether to implement null check

9. Type checking and conversion

1. Type checking

The is and !is operators are used to check whether an object conforms to a given type at runtime:

fun main() {
    
    
    val strValue = "leavesC"
    parserType(strValue) //value is String , length : 7
    val intValue = 100
    parserType(intValue) //value is Int , toLong : 100
    val doubleValue = 100.22
    parserType(doubleValue) //value !is Long
    val longValue = 200L
    parserType(longValue) //unknown
}

fun parserType(value: Any) {
    
    
    when (value) {
    
    
        is String -> println("value is String , length : ${
      
      value.length}")
        is Int -> println("value is Int , toLong : ${
      
      value.toLong()}")
        !is Long -> println("value !is Long")
        else -> println("unknown")
    }
}

At the same time, the is operator also comes with a smart conversion function. In many cases there is no need to use explicit conversion operators in kotlin because the compiler tracks is checks for immutable values ​​along with explicit conversions and automatically inserts safe conversions when needed

For example, in the above example, when it is judged that the value is a String type, you can directly use the value as a String type variable and call its internal properties. This process is called smart conversion

The compiler will specify to intelligently convert the variable to the appropriate type according to the context

if (value !is String) return
//如果 value 非 String 类型时直接被 return 了,所以此处可以直接访问其 length 属性
println(value.length)

// || 右侧的 value 被自动隐式转换为字符串,所以可以直接访问其 length 属性
if (value !is String || value.length > 0) {
    
    

}

// && 右侧的 value 被自动隐式转换为字符串,所以可以直接访问其 length 属性
if (value is String && value.length > 0) {
    
    

}

2. Unsafe conversion operators

The conversion operator asthrows an exception if conversion is not possible. Therefore, we call the unsafe conversion operator

fun main() {
    
    
    parserType("leavesC") //value is String , length is 7
    parserType(10) //会抛出异常 ClassCastException
}

fun parserType(value: Any) {
    
    
    val tempValue = value as String
    println("value is String , length is ${
      
      tempValue.length}")
}

It should be noted that null cannot be converted to a String variable because the type is not nullable

So the following conversion will throw an exception

val x = null
val y: String = x as String //会抛出异常 TypeCastException

Convertible types are declared as nullable for match safety

val x = null
val y: String? = x as String?

3. Safe conversion operators

You can use the safe conversion operator as? to avoid throwing an exception when converting, which returns null on failure

val x = null
val y: String? = x as? String

Although the right side of the above example as? is a non-null type String, the result of the conversion is nullable

Ten, class

1. Basic concepts

The concept of a class is to encapsulate data and the code to process it into a single entity. In Java, data is stored in a private field, by providing accessor methods: getter and setter to access or modify the data

The following sample code is very common in Java, the Point class contains a lot of repetitive code: assigning parameters to fields with the same name through constructors, and getting property values ​​​​through getters

public final class Point {
    
    

   private final int x;
   
   private final int y;
   
   public Point(int x, int y) {
    
    
      this.x = x;
      this.y = y;
   }

   public final int getX() {
    
    
      return this.x;
   }

   public final int getY() {
    
    
      return this.y;
   }
   
}

Using kotlin to declare the Point class requires only one line of code, and the two are completely equivalent

class Point(val x: Int, val y: Int)

Kotlin also uses the keyword class to declare a class. A class declaration consists of a class name, a class header (specify its type parameters, primary constructor, etc.) and a class body surrounded by curly braces. Both the class header and the class body are optional. If a class has no body, the curly braces can be omitted. In addition, classes in kotlin are published (public) and final (non-inheritable) by default

Kotlin distinguishes between primary constructors (declared outside the class body) and secondary constructors (declared inside the class body) . A class can have one primary constructor and multiple secondary constructors. It also allows additional constructors to be initadded to the initialization code block. initialization logic

2. The main constructor

The primary constructor is part of the class header, following the class name (and optional type parameters). The parameters of the primary constructor can be variable (var) or read-only (val)

class Point constructor(val x: Int, val y: Int) {
    
    

}

If the primary constructor does not have any annotations or visibility modifiers, the constructor keyword can be omitted

class Point(val x: Int, val y: Int) {
    
    

}

//如果不包含类体,则可以省略花括号
class Point(val x: Int, val y: Int)

The constructor keyword is required if the constructor has annotations or visibility modifiers and those modifiers precede it

class Point public @Inject constructor(val x: Int, val y: Int) {
    
    

}

The main constructor cannot contain any code. The initialization code can be placed in the initializer blocks prefixed with the init keyword. The initialization block contains the code that is executed when the class is created. The parameters of the main constructor can be in Used in initialization blocks. You can also declare multiple initializer blocks within a class if desired. It should be noted that if the parameters of the constructor are modified with val/var, it is equivalent to declaring a global attribute with the same name inside the class. If you do not add val/var for modification, the constructor can only be referenced when the init function block and global attributes are initialized

In addition, to create an instance of a class, you don't need to use the new keyword in Java, just call the constructor like a normal function

class Point(val x: Int, val y: Int) {
    
    

    init {
    
    
        println("initializer blocks , x value is: $x , y value is: $y")
    }

}

fun main() {
    
    
    Point(1, 2) // initializer blocks , x value is: 1 , y value is: 2
}

The parameters of the primary constructor can also be used in the property initializers declared in the class body

class Point(val x: Int, val y: Int) {
    
    

    private val localX = x + 1

    private val localY = y + 1

    init {
    
    
        println("initializer blocks , x value is: $x , y value is: $y")
        println("initializer blocks , localX value is: $localX , localY value is: $localY")
    }

}

fun main() {
    
    
    Point(1, 2)
    //initializer blocks , x value is: 1 , y value is: 2
    //initializer blocks , localX value is: 2 , localY value is: 3
}

3. Secondary constructor

Classes can also declare secondary constructors prefixed with constructor. If the class has a primary constructor, each secondary constructor needs to be directly delegated to the primary constructor or to another secondary constructor for indirect delegation, which can be specified with the this keyword

class Point(val x: Int, val y: Int) {
    
    

    private val localX = x + 1

    private val localY = y + 1

    init {
    
    
        println("initializer blocks , x value is: $x , y value is: $y")
        println("initializer blocks , localX value is: $localX , localY value is: $localY")
    }

    constructor(base: Int) : this(base + 1, base + 1) {
    
    
        println("constructor(base: Int)")
    }

    constructor(base: Long) : this(base.toInt()) {
    
    
        println("constructor(base: Long)")
    }

}

fun main() {
    
    
    Point(100)
    //initializer blocks , x value is: 101 , y value is: 101
    //initializer blocks , localX value is: 102 , localY value is: 102
    //constructor(base: Int)
    Point(100L)
    //initializer blocks , x value is: 101 , y value is: 101
    //initializer blocks , localX value is: 102 , localY value is: 102
    //constructor(base: Int)
    //constructor(base: Long)
}

The code in the initialization block will actually become part of the primary constructor, and the delegation to the primary constructor will be the first statement of the secondary constructor, so all code in the initialization block will be executed before the body of the secondary constructor. Even if the class does not have a primary constructor, this delegation still happens implicitly and the initialization block is still executed. If a non-abstract class does not declare any (primary or secondary) constructor, a public primary constructor with no parameters will be generated by default

4. Properties

In Java, the combination of a field and its accessors is called a property. In kotlin, properties are a first-class language feature, completely replacing fields and accessor methods. Declaring a property in a class is the same as declaring a variable using the val and var keywords. val variable has only one getter, var variable has both getter and setter

fun main() {
    
    
    val user = User()
    println(user.name)
    user.age = 200
}

class User() {
    
    

    val name: String = "leavesC"

    var age: Int = 25

}

5. Custom accessor

The default implementation logic for an accessor is simple: create a field to store the value, a getter that returns the property's value, and a setter that updates the property's value. You can also customize the accessor if you want

For example, the following declares three properties with custom accessors

class Point(val x: Int, val y: Int) {
    
    

    val isEquals1: Boolean
        get() {
    
    
            return x == y
        }

    val isEquals2
        get() = x == y

    var isEquals3 = false
        get() = x > y
        set(value) {
    
    
            field = !value
        }

}

If you only need to change the visibility of an accessor or add annotations to it, you can define the accessor without defining its implementation

fun main() {
    
    
    val point = Point(10, 10)
    println(point.isEquals1)
    //以下代码会报错
    //point.isEquals1 = true
}

class Point(val x: Int, val y: Int) {
    
    

    var isEquals1: Boolean = false
        get() {
    
    
            return x == y
        }
        private set
    
}

6. Lazy initialization

Generally, properties of non-empty types must be initialized in the constructor, but it is very inconvenient for projects that use a dependency injection framework such as Dagger2. In order to deal with this situation, you can use the lateinit modifier to mark the property to tell the compiler that the property will be initialized at a later time

Properties or variables decorated with lateinit must be of non-null type and cannot be primitive types

class Point(val x: Int, val y: Int)

class Example {
    
    

    lateinit var point: Point

    var point2: Point

    constructor() {
    
    
        point2 = Point(10, 20)
    }
    
}

If an uninitialized lateinit variable is accessed, an exception message will be thrown containing the specific reason (the variable is uninitialized)

Exception in thread "main" kotlin.UninitializedPropertyAccessException: lateinit property point has not been initialized

Eleven, class classification

1. Abstract class

A class declared as abstract can contain member methods without an implementation body, and the member methods are also marked with abstract. This kind of class is called an abstract class, and a method without an implementation body is called an abstract method.

Also, we don't need to annotate an abstract class or abstract method with open, since this is declared by default

abstract class BaseClass {
    
    
    abstract fun fun1()
}

class Derived : BaseClass() {
    
    
    override fun fun1() {
    
    
        
    }
}

2. Data class

Data classes are a very powerful class that avoids duplicating the boilerplate code of POJOs in Java that are used to save state but operate very simply, they usually only provide simple getters and setters for accessing their properties

Defining a new data class is as simple as

data class Point(val x: Int, val y: Int)

By default, the data class generates the following methods for all properties declared in the main constructor

  • getter, setter (needs to be var)
  • componentN(). Correspond to the order of attribute declarations of the primary constructor
  • copy()
  • toString()
  • hashCode()
  • equals()

To ensure consistent and meaningful behavior in the generated code, data classes must meet the following requirements:

  • The primary constructor needs to contain a parameter
  • All parameters of the primary constructor need to be marked as val or var
  • Data classes cannot be abstract, open, sealed or internal

You can use IDEA to decompile and view the Java implementation of the Point class to understand its internal implementation

public final class Point {
    
    
   private final int x;
   private final int y;

   public final int getX() {
    
    
      return this.x;
   }

   public final int getY() {
    
    
      return this.y;
   }

   public Point(int x, int y) {
    
    
      this.x = x;
      this.y = y;
   }

   public final int component1() {
    
    
      return this.x;
   }

   public final int component2() {
    
    
      return this.y;
   }

   @NotNull
   public final Point copy(int x, int y) {
    
    
      return new Point(x, y);
   }

   // $FF: synthetic method
   // $FF: bridge method
   @NotNull
   public static Point copy$default(Point var0, int var1, int var2, int var3, Object var4) {
    
    
      if ((var3 & 1) != 0) {
    
    
         var1 = var0.x;
      }

      if ((var3 & 2) != 0) {
    
    
         var2 = var0.y;
      }

      return var0.copy(var1, var2);
   }

   public String toString() {
    
    
      return "Point(x=" + this.x + ", y=" + this.y + ")";
   }

   public int hashCode() {
    
    
      return this.x * 31 + this.y;
   }

   public boolean equals(Object var1) {
    
    
      if (this != var1) {
    
    
         if (var1 instanceof Point) {
    
    
            Point var2 = (Point)var1;
            if (this.x == var2.x && this.y == var2.y) {
    
    
               return true;
            }
         }

         return false;
      } else {
    
    
         return true;
      }
   }
}

Many common operations can be simplified through data classes, which can be easily performed: formatting output variable values, mapping objects to variables, comparing equality between variables, copying variables, etc.

fun main() {
    
    
    val point1 = Point(10, 20)
    val point2 = Point(10, 20)
    println("point1 toString() : $point1") //point1 toString() : Point(x=10, y=20)
    println("point2 toString() : $point2") //point2 toString() : Point(x=10, y=20)

    val (x, y) = point1
    println("point1 x is $x,point1 y is $y") //point1 x is 10,point1 y is 20

    //在 kotlin 中,“ == ” 相当于 Java 的 equals 方法
    //而 “ === ” 相当于 Java 的 “ == ” 方法
    println("point1 == point2 : ${
      
      point1 == point2}") //point1 == point2 : true
    println("point1 === point2 : ${
      
      point1 === point2}") //point1 === point2 : false

    val point3 = point1.copy(y = 30)
    println("point3 toString() : $point3") //point3 toString() : Point(x=10, y=30)
}

It should be noted that toString()、equals()、hashCode()、copy()the methods of the data class only consider the properties declared in the main constructor, so there may be some unexpected results when comparing two data class objects

data class Point(val x: Int) {
    
    

    var y: Int = 0

}

fun main() {
    
    
    val point1 = Point(10)
    point1.y = 10

    val point2 = Point(10)
    point2.y = 20

    println("point1 == point2 : ${
      
      point1 == point2}") //point1 == point2 : true
    println("point1 === point2 : ${
      
      point1 === point2}") //point1 === point2 : false
}

3. Sealing

The Sealed class (sealed class) is used to limit the possible subclasses of the class. The direct subclass of the class modified with Sealed is only allowed to be defined in the file where the Sealed class is located (the indirect successor of the sealed class can be defined in other files Middle), which helps developers to grasp the changing relationship between the parent class and the subclass, avoiding potential bugs caused by code changes, and the constructor of the sealed class can only be private

For example, for the View class, its subclass can only be defined in the same file as it, and the class modified by the Sealed modifier also implicitly indicates that the class is an open class, so there is no need to explicitly add the open modifier

sealed class View {
    
    

    fun click() {
    
    

    }

}

class Button : View() {
    
    

}

class TextView : View() {
    
    

}

Because subclasses of the Sealed class are controllable to the compiler, if all subclasses of the Sealed class are processed in the when expression, then there is no need to provide an else default branch. Even if a View subclass is added due to business changes in the future, the compiler will detect that the check method lacks branch checks and report an error, so the check method is type-safe

fun check(view: View): Boolean {
    
    
    when (view) {
    
    
        is Button -> {
    
    
            println("is Button")
            return true
        }
        is TextView -> {
    
    
            println("is TextView")
            return true
        }
    }
}

4. Enumeration class

Kotlin also provides the implementation of enumeration. Compared with Java, it needs to use the class keyword to declare enumeration.

enum class Day {
    
    
    SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY
}

Enums can declare some parameters

enum class Day(val index: Int) {
    
    
    SUNDAY(0), MONDAY(1), TUESDAY(2), WEDNESDAY(3), THURSDAY(4), FRIDAY(5), SATURDAY(6)
}

Additionally, enums can also implement the interface

interface OnChangedListener {
    
    

    fun onChanged()

}

enum class Day(val index: Int) : OnChangedListener {
    
    
    SUNDAY(0) {
    
    
        override fun onChanged() {
    
    

        }
    },
    MONDAY(1) {
    
    
        override fun onChanged() {
    
    
            
        }
    }
}

Enumerations also contain some public functions

fun main() {
    
    
    val day = Day.FRIDAY
    //获取值
    val value = day.index  //5
    //通过 String 获取相应的枚举值
    val value1 = Day.valueOf("SUNDAY") //SUNDAY
    //获取包含所有枚举值的数组
    val value2 = Day.values()
    //获取枚举名
    val value3 = Day.SUNDAY.name //SUNDAY
    //获取枚举声明的位置
    val value4 = Day.TUESDAY.ordinal //2
}

5. Nested classes

In kotlin, the class defined in the class is a nested class by default. At this time, the nested class will not contain implicit references to external classes.

class Outer {
    
    

    private val bar = 1

    class Nested {
    
    
        fun foo1() = 2
        //错误
        //fun foo2() = bar
    }
}

fun main() {
    
    
    val demo = Outer.Nested().foo1()
}

After the above code is decompiled by IDEA, you can see its internal Java implementation

It can be seen that Nested is actually a static class, so foo2() cannot access the non-static members of the outer class, and there is no need to declare the Outer variable first and then point to the Nested class, but directly point to the Nested class through the Outer class

public final class Outer {
    
    
   private final int bar = 1;

   public static final class Nested {
    
    
      public final int foo1() {
    
    
         return 2;
      }
   }
}

public final class MainkotlinKt {
    
    
   public static final void main(@NotNull String[] args) {
    
    
      Intrinsics.checkParameterIsNotNull(args, "args");
      int demo = (new Outer.Nested()).foo1();
   }
}

6. Internal classes

If you need to access the members of the outer class, you need to mark the nested class with the inner modifier, which is called an inner class. Inner classes implicitly hold references to outer classes

class Outer {
    
    

    private val bar = 1

    inner class Nested {
    
    
        fun foo1() = 2
        fun foo2() = bar
    }
}

fun main() {
    
    
    val demo = Outer().Nested().foo2()
}

Let's look at its internal Java implementation

After using inner to declare the Nested class, it is equivalent to declaring it as a non-static inner class, so foo2() can access the non-static members of its outer class. Before declaring the Nested variable, it also needs to use the Outer variable to point to its inner Nested kind

public final class Outer {
    
    
   private final int bar = 1;

   public final class Nested {
    
    
      public final int foo1() {
    
    
         return 2;
      }

      public final int foo2() {
    
    
         return Outer.this.bar;
      }
   }
}

public final class MainkotlinKt {
    
    
   public static final void main(@NotNull String[] args) {
    
    
      Intrinsics.checkParameterIsNotNull(args, "args");
      int demo = (new Outer().new Nested()).foo2();
   }
}
Class A is declared in class B in java in kotlin
Nested classes (do not store references to outer classes) static class A class A
Inner classes (store references to outer classes) class A inner class A

7. Anonymous inner class

Object expressions can be used to create anonymous inner class instances

interface OnClickListener {
    
    

    fun onClick()

}

class View {
    
    

    fun setClickListener(clickListener: OnClickListener) {
    
    

    }

}

fun main() {
    
    
    val view = View()
    view.setClickListener(object : OnClickListener {
    
    
        override fun onClick() {
    
    

        }

    })
}

8. Inline classes

Sometimes, we need to wrap native types in order to improve the robustness of the program. For example, for sendEmailthe input parameters of the method, we cannot strictly limit the meaning type of the input parameters. Some developers may understand delay as the unit of milliseconds, and some developers may understand it as the unit of minutes

fun sendEmail(delay: Long) {
    
    
    println(delay)
}

In order to improve the robustness of the program, we can declare a wrapper class as the parameter type:

fun sendEmail(delay: Time) {
    
    
    println(delay.second)
}

class Time(val second: Long)

class Minute(private val count: Int) {
    
    

    fun toTime(): Time {
    
    
        return Time(count * 60L)
    }

}

fun main() {
    
    
    sendEmail(Minute(10).toTime())
}

In this way, the types of parameters that developers can pass in are limited at the source of the code, and developers can directly express the time they want through the class name. However, this method introduces runtime performance overhead due to the additional heap memory allocation problem. The performance consumption required by the new wrapper class is much higher than that of the native type, but at this time, the robustness and reliability of the program need to be considered. Readability, so wrapper classes are also needed

Inline class (InlineClass) was born to solve the contradiction between the two. The above code can be changed to the following way to achieve

fun sendEmail(delay: Time) {
    
    
    println(delay.second)
}

inline class Time(val second: Long)

inline class Minute(private val count: Int) {
    
    

    fun toTime(): Time {
    
    
        return Time(count * 60L)
    }

}

fun main() {
    
    
    sendEmail(Minute(10).toTime())
}

A class modified with inline is called an inline class. The inline class must contain a unique attribute that is initialized in the main constructor. This unique attribute will be used to represent the instance of the inline class at runtime, thus avoiding the wrapping class in runtime overhead

For example, by looking at the bytecode, you can see sendEmailthat the method will be interpreted as a function with a long type as the input type, and does not contain any objects

public static final void sendEmail_G1aXmDY/* $FF was: sendEmail-G1aXmDY*/(long delay) {
    
    
  boolean var4 = false;
  System.out.println(delay);
}

12. Interface

1. Abstract method and default method

The interface in kotlin is similar to that in Java 8. It can contain the definition of abstract methods and the implementation of non-abstract methods. It is not necessary to use the default keyword to mark non-abstract methods with default implementations, but it is necessary to use when implementing abstract methods of interfaces override for labeling

fun main() {
    
    
    val view = View()
    view.click()
    view.longClick()
}

class View : Clickable {
    
    
    
    override fun click() {
    
    
        println("clicked")
    }

}

interface Clickable {
    
    
    fun click()
    fun longClick() = println("longClicked")
}

If a class implements multiple interfaces, and the interfaces contain methods with default implementations and the same signature, the compiler will require developers to explicitly implement the method, and you can choose to call the corresponding methods of different interfaces in this method. accomplish

class View : Clickable, Clickable2 {
    
    

    override fun click() {
    
    
        println("clicked")
    }

    override fun longClick() {
    
    
        super<Clickable>.longClick()
        super<Clickable2>.longClick()
    }
}

interface Clickable {
    
    
    fun click()
    fun longClick() = println("longClicked")
}

interface Clickable2 {
    
    
    fun click()
    fun longClick() = println("longClicked2")
}

2. Abstract attributes

The interface can contain abstract property declarations. The interface does not define whether the abstract property should be stored in a backing field or obtained through a getter. The interface itself does not contain any state, so only classes implementing this interface will store this when needed. value

Look at the following example, both the Button class and the TextView class implement the Clickable interface, and both provide a way to obtain the statusValue value

The Button class provides a custom getter for re-acquiring the statusValue value each time it is accessed, so its value may be inconsistent when the property value is obtained multiple times, because the getRandom() method will be called each time

The statusValue attribute in the TextView class has a backing field to store the data obtained when the class is initialized, so its value will not get the value again after initialization, that is, getRandom() in the TextView class will only be called once

fun main() {
    
    
    val button = Button()
    println(button.statusValue)
    val textView = TextView()
    println(textView.statusValue)
}

class Button : Clickable {
    
    

    override val statusValue: Int
        get() = getRandom()

    private fun getRandom() = Random().nextInt(10)

}

class TextView : Clickable {
    
    

    override val statusValue: Int = getRandom()

    private fun getRandom() = Random().nextInt(10)

}

interface Clickable {
    
    

    val statusValue: Int

}

In addition to being able to declare abstract properties, interfaces can also contain properties with getters and setters, as long as they do not refer to a backing field (backing fields need to store state in the interface, which is not allowed)

interface Clickable {
    
    

    val statusValue: Int

    val check: Boolean
        get() = statusValue > 10
    
}

Thirteen, SAM interface

For the following example, the second way of writing is not supported before Kotlin 1.4, we must fully implement SelfRunnable to call setRunnablethe method

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
interface SelfRunnable {
    
    

    fun run()

}

fun setRunnable(selfRunnable: SelfRunnable) {
    
    
    selfRunnable.run()
}

fun main() {
    
    
    setRunnable(object : SelfRunnable {
    
    
        override fun run() {
    
    
            println("hello,leavesC")
        }
    })
    //错误,Kotlin 1.4 之前不支持
//    setRunnable {
    
    
//        println("hello,leavesC")
//    }
}

After Kotlin 1.4, Kotlin began to support SAM conversion . An interface with only one abstract method is called a functional interface or SAM (Single Abstract Method) interface , and a functional interface can have multiple non-abstract members, but only one abstract member. SAM conversion means Single Abstract Method Conversionsthat for an interface with only a single non-default abstract method, it can be directly represented by Lambda, provided that the function type represented by Lambda can match the method signature in the interface

Therefore, after Kotlin 1.4, it is supported to directly declare the implementation class of SelfRunnable in the form of Lambda, so that the method call can be more concise, but this also requires the interface to be modified with the fun keyword at the same time

/**
 * @Author: leavesCZY
 * @Github:https://github.com/leavesCZY
 */
fun interface SelfRunnable {
    
    

    fun run()

}

fun setRunnable(selfRunnable: SelfRunnable) {
    
    
    selfRunnable.run()
}

fun main() {
    
    
    setRunnable {
    
    
        println("hello,leavesC")
    }
}

14. Inheritance

All classes in kotlin have a common superclass Any , which is the default superclass for classes without a superclass declaration. It should be noted that Any is not java.lang.Object , it has no other properties or functions except equals(), hashCode() and toString()

To declare an explicit superclass, put the parent class name after the colon in the class header

open class Base()

class SubClass() : Base()

Among them, the open annotation on the class is opposite to the final meaning in Java, and is used to allow other classes to inherit from this class. By default, all classes in kotlin are final

If the derived class has a primary constructor, its base type must directly or indirectly call the primary constructor of the base class

open class Base(val str: String)

class SubClass(val strValue: String) : Base(strValue)

class SubClass2 : Base {
    
    

    constructor(strValue: String) : super(strValue)

    constructor(intValue: Int) : super(intValue.toString())

    constructor(doubValue: Double) : this(doubValue.toString())

}

1. Coverage method

Unlike Java, kotlin requires explicit annotation of overridable and overridden members:

open class Base() {
    
    
    open fun fun1() {
    
    

    }

    fun fun2() {
    
    
        
    }
}

class SubClass() : Base() {
    
    
    override fun fun1() {
    
    
        super.fun1()
    }
}

Only functions marked with open can be overloaded by subclasses, and subclasses use override to indicate that the function is to override the function with the same signature of the parent class. A member marked override is itself open, that is, it can be overridden by subclasses. If you want to prohibit re-overwriting, you can use the final keyword to mark.
If the parent class does not use open to mark the function, the subclass is not allowed to define functions with the same signature. For a final class (a class not marked with open), it is meaningless to use open to mark properties and methods

2. Attribute coverage

Property overriding is similar to method overriding. Properties declared as open in the superclass must be redeclared in the derived class beginning with override if they are to be overridden, and they must have compatible types

Each declared property can be overridden by a property with an initializer or a property with a getter method

open class Base {
    
    
    open val x = 10

    open val y: Int
        get() {
    
    
            return 100
        }
}

class SubClass : Base() {
    
    
    override val x = 100

    override var y = 200
}

fun main() {
    
    
    val base = Base()
    println(base.x) //10
    println(base.y) //100

    val base1: Base = SubClass()
    println(base1.x) //100
    println(base1.y) //200

    val subClass = SubClass()
    println(subClass.x) //100
    println(subClass.y) //200
}

Also, it is possible to override a val attribute with a var attribute, but not vice versa. Because a val property essentially declares a getter method, and overriding it as var simply declares an additional setter method in the subclass

You can use the override keyword in the primary constructor as part of the property declaration

open class Base {
    
    
    open val str: String = "Base"
}

class SubClass(override val str: String) : Base()

fun main() {
    
    
    val base = Base()
    println(base.str) //Base

    val subClass = SubClass("leavesC")
    println(subClass.str) //leavesC
}

3. Call the superclass implementation

Derived classes can use the super keyword to call the implementation of functions and property accessors of their superclasses

open class BaseClass {
    
    
    open fun fun1() {
    
    
        println("BaseClass fun1")
    }
}

class SubClass : BaseClass() {
    
    

    override fun fun1() {
    
    
        super.fun1()
    }

}

For the inner class, it can directly call the function of the outer class

open class BaseClass2 {
    
    
    private fun fun1() {
    
    
        println("BaseClass fun1")
    }

    inner class InnerClass {
    
    
        fun fun2() {
    
    
            fun1()
        }
    }

}

But if you want to access the superclass of the outer class in an inner class, you need to use the super keyword qualified by the outer class name to achieve

open class BaseClass {
    
    
    open fun fun1() {
    
    
        println("BaseClass fun1")
    }
}

class SubClass : BaseClass() {
    
    

    override fun fun1() {
    
    
        println("SubClass fun1")
    }

    inner class InnerClass {
    
    

        fun fun2() {
    
    
            super@SubClass.fun1()
        }

    }

}

fun main() {
    
    
    val subClass = SubClass()
    val innerClass = subClass.InnerClass()
    //BaseClass fun1
    innerClass.fun2()
}

If a class inherits multiple implementations of the same member from its immediate superclass and implemented interfaces, it must override this member and provide its own implementation to disambiguate

To indicate which supertype to inherit from, use super qualified by the supertype name in angle brackets to specify, such as super<BaseClass>

open class BaseClass {
    
    
    open fun fun1() {
    
    
        println("BaseClass fun1")
    }
}

interface BaseInterface {
    
    
    //接口成员默认就是 open 的
    fun fun1() {
    
    
        println("BaseInterface fun1")
    }
}

class SubClass() : BaseClass(), BaseInterface {
    
    
    override fun fun1() {
    
    
        //调用 SubClass 的 fun1() 函数
        super<BaseClass>.fun1()
        //调用 BaseInterface 的 fun1() 函数
        super<BaseInterface>.fun1()
    }
}

15. Collection

1. Read-only collections and mutable collections

Another feature of kotlin's collection design that is different from Java is that kotlin separates the interface for accessing data from the interface for modifying collection data. The kotlin.collections.Collectioninterface provides operations such as traversing collection elements, obtaining the size of the collection, and judging whether the collection contains an element . This interface does not provide methods for adding and removing elements . kotlin.collections.MutableCollectionThe interface inherits from kotlin.collections.Collectionthe interface and extends methods for adding, removing, and clearing elements

Just like kotlin's distinction between valand var, the separation of the read-only collection interface and the mutable collection interface can improve the controllability of the code. If the function receives Collectionas a formal parameter, then you can know that the function will not modify the collection, but only data to be read

Following are the functions used to create different types of collections

set element read only variable
List listOf mutableListOf、arrayListOf
Set setOf mutableSetOf、hashSetOf、linkedSetOf、sortedSetOf
Map mapOf mutableMapOf、hashMapOf、linkedMapOf、sortedMapOf
val list = listOf(10, 20, 30, 40)
//不包含 add 方法
//list.add(100)
println(list.size)
println(list.contains(20))

val mutableList = mutableListOf("leavesC", "leavesc", "叶")
mutableList.add("Ye")
println(mutableList.size)
println(mutableList.contains("leavesC"))

2. Collections and Java

Because Java does not distinguish between read-only collections and mutable collections, even if a collection is declared read-only in kotlin, Java code can modify the collection, and collections in Java code are also mutability unknown to kotlin, kotlin The code can treat it as read-only or mutable, and the contained elements can also be null or not null

For example, in Java code, names is a variable of type List<String>

public class JavaMain {
    
    

    public static List<String> names = new ArrayList<>();

    static {
    
    
        names.add("leavesC");
        names.add("Ye");
    }

}

There are four ways to refer to variable names in kotlin

val list1: List<String?> = JavaMain.names

val list2: List<String> = JavaMain.names

val list3: MutableList<String> = JavaMain.names

val list4: MutableList<String?> = JavaMain.names

3. Mutability of read-only collections

A read-only collection is not necessarily immutable. For example, suppose there is an object with a read-only type interface, and there are two different references to the object, one read-only and the other mutable. When the mutable reference modifies the object, this is equivalent to the read-only reference "The read-only collection was modified", so read-only collections are not always thread-safe. If you need to process data in a multi-threaded environment, you need to ensure that access to the data is correctly synchronized, or use a data structure that supports concurrent access

For example, list1 and list1 refer to the same collection object, and the modification of list3 to the collection will also affect list1

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

4. Collections and Nullability

The nullability of collections can be divided into three types:

  1. collection elements that may contain null
  2. The collection itself can be null
  3. The collection itself may be null and may contain collection elements that are null

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

//List<Int?> 是能持有 Int? 类型值的列表
val intList1: List<Int?> = listOf(10, 20, 30, 40, null)
//List<Int>? 是可以为 null 的列表
var intList2: List<Int>? = listOf(10, 20, 30, 40)
intList2 = null
//List<Int?>? 是可以为 null 的列表,且能持有 Int? 类型值
var intList3: List<Int?>? = listOf(10, 20, 30, 40, null)
intList3 = null

16. Extension functions and extension attributes

1. Extension function

Extension functions are used to add a new behavior to a class, which is a way to extend classes that lack useful functions. The purpose of extension functions is similar to that of static utility methods implemented in Java. One of the advantages of using extension functions in kotlin is that we don't need to pass in the entire object as a parameter when calling the method. The extension function behaves like it belongs to the class itself, and can use the this keyword and call it directly All its public methods

Extension functions do not allow you to break its encapsulation. Unlike methods defined inside a class, extension functions cannot access private or protected members.

//为 String 类声明一个扩展函数 lastChar() ,用于返回字符串的最后一个字符
//get方法是 String 类的内部方法,length 是 String 类的内部成员变量,在此处可以直接调用
fun String.lastChar() = get(length - 1)

//为 Int 类声明一个扩展函数 doubleValue() ,用于返回其两倍值
//this 关键字代表了 Int 值本身
fun Int.doubleValue() = this * 2

After that, we can directly call the extension function just like calling the method declared inside the class itself

fun main() {
    
    
    val name = "leavesC"
    println("$name lastChar is: " + name.lastChar())

    val age = 24
    println("$age doubleValue is: " + age.doubleValue())
}

If you need to declare a static extension function, you must define it on the companion object, so that you can call its extension function without a Namer instance, just like calling a Java static function

class Namer {
    
    

    companion object {
    
    

        val defaultName = "mike"

    }

}

fun Namer.Companion.getName(): String {
    
    
    return defaultName
}

fun main() {
    
    
    Namer.getName()
}

It should be noted that if the extension function is declared inside the class, the extension function can only be called inside the class and its subclasses, because at this time it is equivalent to declaring a non-static function, which cannot be referenced outside. So generally the extension function is declared as a global function

2. Extended attributes

Extension functions can also be used on properties

//扩展函数也可以用于属性
//为 String 类新增一个属性值 customLen
var String.customLen: Int
    get() = length
    set(value) {
    
    
        println("set")
    }

fun main() {
    
    
    val name = "leavesC"
    println(name.customLen)
    name.customLen = 10
    println(name.customLen)
    //7
    //set
    //7
}

3. Non-rewritable extension functions

Look at the following example, the subclass Button rewrites the click() function of the parent class View. At this time, if you declare a View variable and assign it to an object of Button type, the click() function called will be the method rewritten by the Button class.

fun main() {
    
    
    val view: View = Button()
    view.click() //Button clicked
}

open class View {
    
    
    open fun click() = println("View clicked")
}

class Button : View() {
    
    
    override fun click() = println("Button clicked")
}

For extension functions, it is different from the above example. If both the base class and the subclass define an extension function with the same name, which extension function to call is determined by the static type of the variable, not the runtime type of the variable

fun main() {
    
    
    val view: View = Button()
    view.longClick() //View longClicked
}

open class View {
    
    
    open fun click() = println("View clicked")
}

class Button : View() {
    
    
    override fun click() = println("Button clicked")
}

fun View.longClick() = println("View longClicked")

fun Button.longClick() = println("Button longClicked")

Also, if a class member function has the same signature as an extension function, the member function will be used first

The extension function does not really modify the original class, but its bottom layer is actually implemented by static import. Extension functions can be declared in any file, so a common practice is to put a series of related functions in a new file

It should be noted that the extension function will not automatically take effect in the entire project scope. If you need to use the extension function, you need to import it

4. Nullable Receiver

Extensions can be defined for nullable receiver types, even if the receiver is null, so that developers do not need to perform null judgment before calling the extension function, and can check this == nullwhether the receiver is empty by

fun main() {
    
    
    var name: String? = null
    name.check() //this == null
    name = "leavesC"
    name.check() //this != null
}

fun String?.check() {
    
    
    if (this == null) {
    
    
        println("this == null")
        return
    }
    println("this != null")
}

Seventeen, Lambda expression

A Lambda expression is essentially a small piece of code that can be passed to other functions. Through a Lambda expression, a common code structure can be extracted into a library function, or a Lambda expression can be stored in a variable and treated as an ordinary function.

//由于存在类型推导,所以以下三种声明方式都是完全相同的
val plus1: (Int, Int) -> Int = {
    
     x: Int, y: Int -> x + y }
val plus2: (Int, Int) -> Int = {
    
     x, y -> x + y }
val plus3 = {
    
     x: Int, y: Int -> x + y }
println(plus3(1, 2))
  1. A lambda expression is always surrounded by curly braces, with an arrow separating the argument list from the function body
  2. If the Lambda declares the function type, then the type declaration of the function body can be omitted
  3. If the Lambda declares the parameter type and the return value supports type deduction, then the function type declaration can be omitted

Although we tend to try to avoid making Lambda expressions refer to external variables to avoid side effects, in some cases letting Lambda refer to external variables can also simplify the calculation structure. Lambda expressions that access external environment variables are called closures, and closures can be passed as parameters or used directly. Unlike Java, closures in kotlin can not only access external variables but also modify them

For example, suppose we need a method that computes a sum and returns the current sum size each time the function is called. There is no variable to save the current sum outside the method, and it is stored inside the Lambda expression

fun main() {
    
    
    val sum = sumFunc()
    println(sum(10)) //10
    println(sum(20)) //30
    println(sum(30)) //60
}

fun sumFunc(): (Int) -> Int {
    
    
    var base = 0
    return fun(va: Int): Int {
    
    
        base += va
        return base
    }
}

In addition, kotlin also supports an autorun syntax

{
    
     va1: Int, va2: Int -> println(va1 + va2) }(10, 20)

The most common use of Lambda expressions is to work with collections, see the following examples

To get the oldest person from a list of persons

data class Person(val name: String, val age: Int)

fun main() {
    
    
    val people = listOf(Person("leavesC", 24), Person("Ye", 22))
    println(people.maxBy {
    
     it.age }) //Person(name=leavesC, age=24)
}

Among them, the library function maxBy can be called on any collection, and it takes one argument: a function that specifies the function to use for comparison. The code in curly braces { it.age }is the Lambda expression that implements this logic

The actual parameter of the above maxBy function is a simplified way of writing, let’s look at the simplification process of the maxBy function

The most primitive syntax statement should look like this, with the Lambda expression wrapped in parentheses

println(people.maxBy({
    
     p: Person -> p.age }))

Kotlin has a grammatical convention that if the Lambda expression is the last argument of a function call, it can be placed outside the parentheses

 println(people.maxBy() {
    
     p: Person -> p.age })

When the Lambda expression is the only actual parameter of the function, the pair of empty parentheses in the calling code can be removed

 println(people.maxBy {
    
     p: Person -> p.age })

When the parameter type of the Lambda expression can be deduced, the declaration of the parameter type can be omitted

println(people.maxBy {
    
     p -> p.age })

If the current context expects a lambda expression with only one parameter and the parameter type can be inferred, a default name is generated for the parameter: it

 println(people.maxBy {
    
     it.age })

A significant difference between kotlin and Java is that in kotlin, Lambda expressions inside functions are not limited to accessing function parameters and final variables, and non-final variables can also be accessed and modified inside Lambda

To access external variables from within Lambda, we say that these variables are captured by Lambda. When capturing a final variable, the variable value is stored with the Lambda code that uses the value. For non-final variables, the value is wrapped in a special wrapper, and a reference to this wrapper is stored with the Lambda code

var number = 0
val list = listOf(10, 20, 30, 40)
list.forEach {
    
    
    if (it > 20) {
    
    
        number++
    }
}
println(number) //2

A member reference is used to create a function value that calls a single method or accesses a single property, and the class name is separated from the name of the member (a method or a property) to be referenced by a double colon

One use of member references is: if the code block to be passed as a parameter has been defined as a function, there is no need to create a Lambda expression to call the function, and the function can be passed directly by member reference (or transfer attribute). Also, member references work just as well for extension functions

data class Person(val name: String, val age: Int) {
    
    

    val myAge = age

    fun getPersonAge() = age
}

fun Person.filterAge() = age

fun main() {
    
    
    val people = listOf(Person("leavesC", 24), Person("Ye", 22))
    println(people.maxBy {
    
     it.age })    //Person(name=leavesC, age=24)
    println(people.maxBy(Person::age))  //Person(name=leavesC, age=24)
    println(people.maxBy(Person::myAge))  //Person(name=leavesC, age=24)
    println(people.maxBy(Person::getPersonAge))  //Person(name=leavesC, age=24)
    println(people.maxBy(Person::filterAge))  //Person(name=leavesC, age=24)
}

Do not put parentheses after the name of a member reference, whether the reference is a function or a property

Additionally, top-level functions can be referenced

fun test() {
    
    
    println("test")
}

fun main() {
    
    
    val t = ::test
}

You can also use the constructor to reference storage or deferred execution of the action of creating a class instance

data class Person(val name: String, val age: Int)

fun main() {
    
    
    val createPerson = ::Person
    val person = createPerson("leavesC", 24)
    println(person)
}

18. Extension functions in the standard library

The kotlin standard library provides several useful extension functions, which are defined under the Standard file

1、run

The run function receives a function parameter and uses the return value of the function as the return value of the run function

@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

Example

fun main() {
    
    
    var nickName = "leavesC"
    nickName = nickName.run {
    
    
        if (isNotEmpty()) {
    
    
            this
        } else {
    
    
            ""
        }
    }
    println(nickName)
}

2、with

The with function is not an extension function, but because it has similar functions, it is introduced here together. The first parameter of the with function is the receiver object receiver, and the second parameter is the extension function defined on the receiver object type, so the public methods and properties of the receiver can be directly called inside the function

@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

The with function is used to perform multiple operations on the same object without repeatedly writing out the name of the object

For example, in order to construct a string containing the specified content, you need to call the following successively

fun main() {
    
    
    val result = StringBuilder()
    result.append("leavesC")
    result.append("\n")
    for (letter in 'A'..'Z') {
    
    
        result.append(letter)
    }
    println(result.toString())
 }

If you use the with function to build it, the code will be much more concise

val result = with(StringBuilder()) {
    
    
    append("leavesC")
    append("\n")
    for (letter in 'A'..'Z') {
    
    
        append(letter)
    }
    toString()
}
println(result)

The with function is a function that takes two parameters, in this case a StringBuilder and a Lambda expression, taking advantage of the convention of putting the Lambda expression outside the parentheses

The return value of the with function is the result of executing the Lambda expression, which is the return value of the last expression in the Lambda, so if the code is modified as shown below, because the println() method has no return value, it will be printed out The content will be kotlin.Unit

val result = with(StringBuilder()) {
    
    
    append("leavesC")
    append("\n")
    for (letter in 'A'..'Z') {
    
    
        append(letter)
    }
    println("Hello")
}
println(result)  //kotin.Unit

3、apply

The apply function is declared as an extension function of type T, its receiver is the receiver of the Lambda as an actual parameter, and the final function returns this which is the object itself

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

So the only difference between the apply function and the with function is that the apply function always returns the object passed to it as an argument

val result = StringBuilder().apply {
    
    
    append("leavesC")
    append("\n")
    for (letter in 'A'..'Z') {
    
    
        append(letter)
    }
    toString()
}
println(result)
println(result.javaClass) //class java.lang.StringBuilder

4、also

The also function receives a parameter of function type, which in turn takes the receiver itself as a parameter, and finally returns the receiver object itself

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

Example

fun main() {
    
    
    val nickName = "leavesC"
    val also = nickName.also {
    
    
        it.length
    }
    println(also) //leavesC
}

5、let

The let function receives a parameter of function type, which in turn takes the receiver itself as a parameter, and finally returns the evaluation result of the function

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    
    
    contract {
    
    
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

Example

fun main() {
    
    
    val nickName = "leavesC"
    val also = nickName.let {
    
    
        it.length
    }
    println(also) //7
}

6、takeIf

takeIf accepts a function whose return type is bool, and returns the receiver object itself when the return value of the parameter is true, otherwise returns null

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    
    
    contract {
    
    
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}

Example

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

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

7、takeUnless

The judgment condition of takeUnless is opposite to that of takeIf, so I won’t go into details here

@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    
    
    contract {
    
    
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}

19. Function Operators

1. Total operator

1、any

Returns true if at least one element meets the given criteria

val list = listOf(1, 3, 5, 7, 9)
println(list.any {
    
     it > 13 })  //false
println(list.any {
    
     it > 7 })   //true

2、all

Returns true if all elements meet the given criteria

val list = listOf(1, 3, 5, 7, 9)
println(list.all {
    
     it > 13 })  //false
println(list.all {
    
     it > 0 })   //true

3、count

Returns the total number of elements that meet the given criteria

val list = listOf(1, 3, 5, 7, 9)
println(list.count {
    
     it > 7 })  //1
println(list.count {
    
     it > 2 })  //4

4、fold

Accumulate all elements by a function from the first item to the last item based on an initial value

fun main() {
    
    
    val list = listOf(1, 3, 5, 7, 9)
    println(list.fold(2) {
    
     total, next->
        println("$next , $total")
        next + total
    })
}
1 , 2
3 , 3
5 , 6
7 , 11
9 , 18
27

5、foldRight

Same as fold, but the order is from last item to first item

val list = listOf(1, 3, 5, 7, 9)
println(list.foldRight(2) {
    
     next, total->
    println("$next , $total")
    next + total
})
9 , 2
7 , 11
5 , 18
3 , 23
1 , 26
27

6、forEach

val list = listOf(1, 3, 5, 7, 9)
list.forEach {
    
     print(it + 1) } //246810

7、forEachIndexed

Similar to forEach, at the same time you can get the index of the element

val list = listOf(1, 3, 5, 7, 9)
list.forEachIndexed {
    
     index, value -> println("$index value is $value") }

0 value is 1
1 value is 3
2 value is 5
3 value is 7
4 value is 9

8、max

Returns the largest item, or null if none

val list = listOf(1, 3, 5, 7, 9)
println(list.max()) //9

9、maxBy

Returns the largest item according to the given function, or null if none

val list = listOf(1, 3, 5, 7, 9)
println(list.maxBy {
    
     -it }) //1

10、min

Returns the smallest item, or null if none

val list = listOf(1, 3, 5, 7, 9)
println(list.min()) //1

11、myCity

Returns the smallest item according to the given function, or null if none

val list = listOf(1, 3, 5, 7, 9)
println(list.minBy {
    
     -it }) //9

12、none

Returns true if no elements match the given function

val list = listOf(1, 3, 5, 7, 9)
println(list.none {
    
     it > 10 }) //true

13、reduce

Same as fold, but without an initial value. Accumulate from first item to last item by a function

val list = listOf(1, 3, 5, 7, 9)
println(list.reduce {
    
     total, next ->
    println("$next , $total")
    total + next
})
3 , 1
5 , 4
7 , 9
9 , 16
25

14、reduceRight

Same as reduce, but the order is from last item to first item

val list = listOf(1, 3, 5, 7, 9)
println(list.reduceRight {
    
     next, total ->
    println("$next , $total")
    total + next
})

7 , 9
5 , 16
3 , 21
1 , 24
25

15、sumBy

Returns the sum of all the data after each item is transformed by the function

val list = listOf(1, 3, 5, 7, 9)
println(list.sumBy {
    
     it + 1 }) //30

2. Filter operator

1、drop

Returns a list containing all elements minus the first n elements

val list = listOf(1, 3, 5, 7, 9)
println(list.drop(2)) //[5, 7, 9]

2、dropWhile

returns the list starting from the first element that does not conform to the given function

val list = listOf(1, 3, 5, 7, 9, 2)
println(list.dropWhile {
    
     it < 4 }) //[5, 7, 9, 2]

3、dropLastWhile

Starting with the last item, returns the list starting from the element that does not conform to the given function

val list = listOf(10, 1, 3, 5, 7, 9)
println(list.dropLastWhile {
    
     it > 4 }) //[10, 1, 3]

4、filter

filter all elements matching the given function condition

val list = listOf(1, 3, 5, 7, 9, 2)
println(list.filter {
    
     it < 4 }) //[1, 3, 2]

5、filterNot

filter all elements that do not meet the given function criteria

val list = listOf(1, 3, 5, 7, 9, 2)
println(list.filterNot {
    
     it < 4 }) //[5, 7, 9]

6、filterNotNull

Filter all elements that are not null

val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.filterNotNull()) //[1, 3, 5, 7, 9, 2]

7、slice

Filter the element of the specified index in a list

val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.slice(listOf(0, 3))) //[1, 7]

8、take

Returns n elements starting from the first

val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.take(2)) //[1, 3]

9、takeLast

Returns n elements starting from the last

val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.takeLast(2)) //[2, null]

10、takeWhile

Returns the elements that meet the given function's criteria starting with the first one.

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.takeWhile {
    
     it > 2 }) //[]
println(list.takeWhile {
    
     it > 0 }) //[1, 3, 5]

3. Mapping operator

1、flatMap

Iterate through all the elements, create a set for each one, and finally put all the sets in one set

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.flatMap {
    
     listOf(it, it + 1) }) //[1, 2, 3, 4, 5, 6, -1, 0, 7, 8, 9, 10, 2, 3]

2、groupBy

Returns a map grouped by the given function

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.groupBy {
    
     listOf(it) }) //{[1]=[1], [3]=[3], [5]=[5], [-1]=[-1], [7]=[7], [9]=[9], [2]=[2]}
println(list.groupBy {
    
     listOf(it, it + 1) }) //{[1, 2]=[1], [3, 4]=[3], [5, 6]=[5], [-1, 0]=[-1], [7, 8]=[7], [9, 10]=[9], [2, 3]=[2]}

3、map

Returns a List with each element transformed according to the given function.

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.map {
    
     listOf(it) }) //[[1], [3], [5], [-1], [7], [9], [2]]
println(list.map {
    
     listOf(it, it + 1) }) //[[1, 2], [3, 4], [5, 6], [-1, 0], [7, 8], [9, 10], [2, 3]]

4、mapIndexed

Returns a List composed of each element converted according to the given function containing the element index

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.mapIndexed {
    
     index, value -> index }) //[0, 1, 2, 3, 4, 5, 6]
println(list.mapIndexed {
    
     index, value -> index * value }) //[0, 3, 10, -3, 28, 45, 12]

5、mapNotNull

Returns a List composed of each non-null element transformed according to the given function

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.mapNotNull {
    
     it }) //[1, 3, 5, -1, 7, 9, 2]

4. Element operator

1、contains

Returns true if the specified element can be found in the collection

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.contains(3)) //true
println(list.contains(13)) //false

2、elementAt

Returns the element corresponding to the given index. If the index array is out of bounds, an IndexOutOfBoundsException will be thrown

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.elementAt(3)) //-1
println(list.elementAt(6)) //null

3、element analysis

Returns the element corresponding to the given index, if the index array is out of bounds, it will return the default value according to the given function

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.elementAtOrElse(3, {
    
     it * 2 }))  //-1
println(list.elementAtOrElse(16, {
    
     it * 2 })) //32

4、elementAtOrNull

Returns the element corresponding to the given index, or returns null if the index array is out of bounds

val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.elementAtOrNull(3))  //-1
println(list.elementAtOrNull(16)) //null

5、first

Returns the first element that meets the given function criteria

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.first {
    
     it % 3 == 0 })  //3

6、firstOrNull

Returns the first element that meets the conditions of the given function, or null if none

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.firstOrNull {
    
     it % 3 == 0 })  //3
println(list.firstOrNull {
    
     it % 8 == 0 })  //null

7、indexOf

Returns the first index of the specified element, or -1 if it does not exist

val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.indexOf(5))  //2
println(list.indexOf(12)) //-1

8、indexOfFirst

Returns the index of the first element that meets the conditions of the given function, or -1 if none

val list = listOf(1, 3, 5, 1, 7, 9, 2)
println(list.indexOfFirst {
    
     it % 2 == 0 })   //6
println(list.indexOfFirst {
    
     it % 12 == 0 })  //-1

9、indexOfLast

Returns the index of the last element that meets the given function's criteria, or -1 if none

val list = listOf(1, 3, 5, 6, 7, 9, 2)
println(list.indexOfLast {
    
     it % 2 == 0 })   //6
println(list.indexOfLast {
    
     it % 12 == 0 })  //-1

10、last

Returns the last element that meets the conditions of the given function

val list = listOf(1, 3, 5, 6, 7, 9, 2)
println(list.last {
    
     it % 2 == 0 })   //2
println(list.last {
    
     it % 3 == 0 })   //9

11、lastIndexOf

Returns the last index of the specified element, or -1 if it does not exist

val list = listOf(1, 3, 2, 6, 7, 9, 2)
println(list.lastIndexOf(2))    //6
println(list.lastIndexOf(12))   //-1

12、lastOrNull

Returns the last element that meets the conditions of the given function, or null if none

val list = listOf(1, 3, 2, 6, 7, 9, 2)
println(list.lastOrNull {
    
     it / 3 == 3 })    //9
println(list.lastOrNull {
    
     it == 10 })       //null

13、single

Returns a single element that matches the given function, or throws an exception if none or more than one

val list = listOf(1, 9, 2, 6, 7, 9, 2)
println(list.single {
    
     it % 7 == 0 })  //7
println(list.single {
    
     it == 2 })      //IllegalArgumentException

14、singleOrNull

Returns a single element that matches the given function, or null if none or more than one

val list = listOf(1, 9, 2, 6, 7, 9, 2)
println(list.singleOrNull {
    
     it % 7 == 0 })  //7
println(list.singleOrNull {
    
     it == 2 })      //null

5. Production Operator

1、partition

Divide a given set into two, the first set is composed of elements whose elements in the original set match the given function condition and returns true, and the second set is composed of each element in the original set matching the given function Elements for which the condition returns false

val list = listOf(1, 9, 2, 6, 7, 9, 2)
val (list1, list2) = list.partition {
    
     it % 2 == 0 }
println(list1)  //[2, 6, 2]
println(list2)  //[1, 9, 7, 9]

2、plus

Returns a collection containing all elements in the original collection and the given collection, because of the name of the function, we can use the + operator

val list1 = listOf(1, 9, 2, 6, 7, 9, 2)
val list2 = listOf(1, 2, 4, 6, 8, 10)
println(list1.plus(list2)) //[1, 9, 2, 6, 7, 9, 2, 1, 2, 4, 6, 8, 10]
println(list1 + list2)  //[1, 9, 2, 6, 7, 9, 2, 1, 2, 4, 6, 8, 10]

3、zip

Returns a List consisting of pairs, each pair consisting of elements of the same index in the two collections. The size of the returned List is determined by the smallest set

val list1 = listOf(1, 9, 2, 6, 7, 9, 2)
val list2 = listOf(1, 2, 4, 6, 8, 10)
val list3 = list1.zip(list2)
println(list3.javaClass)
println(list3.get(0).javaClass)
println("${
      
      list3.get(0).first} , ${
      
      list3.get(0).second}")
list3.forEach {
    
     println(it) }
class java.util.ArrayList
class kotlin.Pair
1 , 1
(1, 1)
(9, 2)
(2, 4)
(6, 6)
(7, 8)
(9, 10)

4、unzip

Generate a Pair containing a List from a List containing a pair

val list1 = listOf(Pair("leavesC", 1), Pair("leavesC_2", 2), Pair("leavesC_3", 3))
val list2 = list1.unzip()
println(list2.javaClass)
println(list2.first)
println(list2.second)
class kotlin.Pair
[leavesC, leavesC_2, leavesC_3]
[1, 2, 3]

6. Sequential operators

1、reverse

Returns a list in the reverse order of the specified list

val list1 = listOf(Pair("leavesC", 1), Pair("leavesC_2", 2), Pair("leavesC_3", 3))
val list2 = list1.reversed()
println(list2)      //[(leavesC_3, 3), (leavesC_2, 2), (leavesC, 1)]

2、sort

Returns a naturally sorted list

val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sorted()
println(list2) //[1, 2, 4, 5, 9, 10]

val list3 = listOf("a", "c", "ab", "b", "cdd", "cda")
val list4 = list3.sorted()
println(list4) //[a, ab, b, c, cda, cdd]

3、sortBy

Returns a list sorted by the specified function

val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sortedBy {
    
     it - 3 }
println(list2) //[1, 2, 4, 5, 9, 10]

4、sortDescending

Returns a List sorted in descending order

val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sortedDescending()
println(list2) //[10, 9, 5, 4, 2, 1]

5、sortDescendingBy

Returns a list sorted in descending order by the specified function

val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sortedByDescending {
    
     it % 2 }
println(list2) //[1, 9, 5, 2, 4, 10]

20. Abnormal

The basic form of exception handling in kotlin is similar to Java

fun compute(index: Int): Boolean {
    
    
    if (index !in 0..10) {
    
    
        throw IllegalArgumentException("参数错误")
    }
    return true
}

Unlike Java, the throw structure in kotlin is an expression that can be used as part of another expression

For example, in the following example, if the condition is not met, an exception will be thrown, resulting in the status variable not being initialized

val status = if (index in 0..10) index else throw IllegalArgumentException("参数错误")

In addition, checked exceptions must be handled explicitly in Java, by either catching the exception with a try/catch statement or throwing it to its caller for processing. Kotlin does not distinguish between checked exceptions and unchecked exceptions, and does not need to specify exceptions thrown by functions, which can be handled or not

In kotlin, the try keyword introduces an expression so that the value of the expression can be assigned to a variable. If a try code block executes normally, the last expression in the code block is the result, if an exception is caught, the last expression in the corresponding catch code block is the result

Look at the following example, if the expression wrapped by the try expression throws an exception, the return value is null, otherwise it is true

fun main() {
    
    
    compute(5)   //fun end : true
    compute(100) //fun end : null
}

fun compute(index: Int) {
    
    
    val status = try {
    
    
        if (index in 0..10) true else throw IllegalArgumentException("参数错误")
    } catch (e: Exception) {
    
    
        null
    }
    println("fun end : " + status)
}

However, if the compute function is ended with a return in the catch statement, nothing is output

fun main() {
    
    
    compute(5)   //fun end : true
    compute(100) //没有任何输出
}

fun compute(index: Int) {
    
    
    val status = try {
    
    
        if (index in 0..10) true else throw IllegalArgumentException("参数错误")
    } catch (e: Exception) {
    
    
        return
    }
    println("fun end : " + status)
}

21. Operator overloading

Kotlin allows to provide predefined operator implementations for types. These operators have fixed symbol representations (such as + and *) and fixed priorities. Operator overloading can map the behavior of operators to specified methods. In order to implement such an operator, it is necessary to provide a member function or extension function with a fixed name for the class, and the corresponding overloaded operator function needs to be marked with the operator modifier

1. Unary operator

operator function
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc()
a– a.dec()

2. Binary operators

operator function
a + b a.plus(b)
a - b a.minus(b)
a * b a.times(b)
a / b a.div(b)
a % b a.rem(b)
a…b a.rangeTo(b)
a in b b.contains(a)
a !in b !b.contains(a)
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b)

3. Array operators

operator function
a[i] a.get(i)
a[i, j] a.get(i, j)
a[i_1, …, i_n] a.get(i_1, …, i_n)
a[i] = b a.set(i, b)
a[i, j] = b a.set(i, j, b)
a[i_1, …, i_n] = b a.set(i_1, …, i_n, b)

4. Equal operator

operator function
a == b a?.equals(b) ?: b === null
a != b !(a?.equals(b) ?: b === null)

The equality operator is a little bit different, and does a more complex transformation to get the correct equality check, because to get an exact function structure comparison, not just specifying the name

The method must be implemented exactly as follows:

operator fun equals(other: Any?): Boolean

The operators === and !== are used for identity checking (they are == and != in Java respectively), and they cannot be overloaded

5. Comparison operators

operator function
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

All comparisons are converted to calls to compareTo, which needs to return Int values

6. Function call

method transfer
a() a.invoke()
a(i) a.invoke(i)
a(i, j) a.invoke(i, j)
a(i_1, …, i_n) a.invoke(i_1, …, i_n)

7. Examples

see a few examples

data class Point(val x: Int, val y: Int) {
    
    

    //+Point
    operator fun unaryPlus() = Point(+x, +y)

    //Point++ / ++Point
    operator fun inc() = Point(x + 1, y + 1)

    //Point + Point
    operator fun plus(point: Point) = Point(x + point.x, y + point.y)

    //Point + Int
    operator fun plus(value: Int) = Point(x + value, y + value)

    //Point[index]
    operator fun get(index: Int): Int {
    
    
        return when (index) {
    
    
            0 -> x
            1 -> y
            else -> throw IndexOutOfBoundsException("无效索引")
        }
    }

    //Point(index)
    operator fun invoke(index: Int) = when (index) {
    
    
        0 -> x
        1 -> y
        else -> throw IndexOutOfBoundsException("无效索引")
    }

}
fun main() {
    
    
    //+Point(x=10, y=-20)  =  Point(x=10, y=-20)
    println("+${
      
      Point(10, -20)}  =  ${
      
      +Point(10, -20)}")

    //Point(x=10, y=-20)++  =  Point(x=10, y=-20)
    var point = Point(10, -20)
    println("${
      
      Point(10, -20)}++  =  ${
      
      point++}")

    //++Point(x=10, y=-20)  =  Point(x=11, y=-19)
    point = Point(10, -20)
    println("++${
      
      Point(10, -20)}  =  ${
      
      ++point}")

    //Point(x=10, y=-20) + Point(x=10, y=-20)  =  Point(x=20, y=-40)
    println("${
      
      Point(10, -20)} + ${
      
      Point(10, -20)}  =  ${
      
      Point(10, -20) + Point(10, -20)}")

    //Point(x=10, y=-20) + 5  =  Point(x=15, y=-15)
    println("${
      
      Point(10, -20)} + ${
      
      5}  =  ${
      
      Point(10, -20) + 5}")

    point = Point(10, -20)
    //point[0] value is: 10
    println("point[0] value is: ${
      
      point[0]}")
    //point[1] value is: -20
    println("point[1] value is: ${
      
      point[1]}")

    //point(0) values is: 10
    println("point(0) values is: ${
      
      point(0)}")
}

22. Infix call and destructuring statement

1. Infix call

A Map variable can be created in the form

fun main() {
    
    
    val maps = mapOf(1 to "leavesC", 2 to "ye", 3 to "https://juejin.cn/user/923245496518439")
    maps.forEach {
    
     key, value -> println("key is : $key , value is : $value") }
}

Use "to" to declare the correspondence between the key and value of the map. This form of function call is called infix call

The declaration of the to function in the kotlin standard library is as follows. It exists as an extension function and is a generic function. The return value Pair finally passes the key and value to the Map through the destructuring declaration.

public infix fun <A, B> A.to(that: B): Pair<A, B> = Pair(this, that)

中缀调用只能与只有一个参数的函数一起使用,无论是普通的函数还是扩展函数。中缀符号需要通过 infix 修饰符来进行标记

fun main() {
    
    
    val pair = 10 test "leavesC"
    val pair2 = 1.2 test 20
    println(pair2.javaClass) //class kotlin.Pair
}

infix fun Any.test(other: Any) = Pair(this, other)

对于 mapOf 函数来说,它可以接收不定数量的 Pair 类型对象,因此我们也可以通过自定义的中缀调用符 test 来创建一个 map 变量

public fun <K, V> mapOf(vararg pairs: Pair<K, V>): Map<K, V> =
    if (pairs.size > 0) pairs.toMap(LinkedHashMap(mapCapacity(pairs.size))) else emptyMap()

 val map = mapOf(10 test "leavesC", 20 test "hello")

2、解构声明

有时会有把一个对象拆解成多个变量的需求,在 kotlin 中这种语法称为解构声明

例如,以下例子将 Person 变量结构为了两个新变量:name 和 age,并且可以独立使用它们

data class Person(val name: String, val age: Int)

fun main() {
    
    
    val (name, age) = Person("leavesC", 24)
    println("Name: $name , age: $age")
    //Name: leavesC , age: 24
}

一个解构声明会被编译成以下代码:

val name = person.component1()
val age = person.component2()

其中的 component1()component2() 函数是在 kotlin 中广泛使用的约定原则的另一个例子。任何表达式都可以出现在解构声明的右侧,只要可以对它调用所需数量的 component 函数即可

需要注意的是,componentN() 函数需要用 operator 关键字标记,以允许在解构声明中使用它们

对于数据类来说,其自动生成了 componentN() 函数,而对非数据类,为了使用解构声明,需要我们自己来手动声明函数

class Point(val x: Int, val y: Int) {
    
    
    operator fun component1() = x
    operator fun component2() = y
}

fun main() {
    
    
    val point = Point(100, 200)
    val (x, y) = point
    println("x: $x , y: $y")
    //x: 100 , y: 200
}

如果我们需要从一个函数返回两个或者更多的值,这时候使用解构声明就会比较方便了

这里使用的是标准类 Pair 来包装要传递的数据,当然,也可以自定义数据类

fun computer(): Pair<String, Int> {
    
    
    //各种计算
    return Pair("leavesC", 24)
}

fun main() {
    
    
    val (name, age) = computer()
    println("Name: $name , age: $age")
}

此外,解构声明也可以用在 for 循环中

val list = listOf(Person("leavesC", 24), Person("leavesC", 25))
for ((name, age) in list) {
    
    
    println("Name: $name , age: $age")
}

对于遍历 map 同样适用

val map = mapOf("leavesC" to 24, "ye" to 25)
for ((name, age) in map) {
    
    
    println("Name: $name , age: $age")
}

同样也适用于 lambda 表达式

val map = mapOf("leavesC" to 24, "ye" to 25)
map.mapKeys {
    
     (key, value) -> println("key : $key , value : $value") }

如果在解构声明中不需要某个变量,那么可以用下划线取代其名称,此时不会调用相应的 componentN() 操作符函数

val map = mapOf("leavesC" to 24, "ye" to 25)
for ((_, age) in map) {
    
    
    println("age: $age")
}

二十三、Object 关键字

1、对象声明

在 kotlin 的世界中,可以通过对象声明这一功能来实现 Java 中的单例模式,将类声明与该类的单一实例声明结合到一起。与类一样,一个对象声明可以包含属性、方法、初始化语句块等的声明,且可以继承类和实现接口,唯一不被允许的是构造方法

与普通类的实例不同,对象声明在定义的时候就被立即创建了,不需要在代码的其它地方调用构造方法,因此为对象声明定义构造方法是没有意义的

interface Fly {
    
    

    fun fly()

}

open class Eat {
    
    

    fun eat() {
    
    
        println("eat")
    }

}

object Animal : Eat(), Fly {
    
    

    override fun fly() {
    
    
        println("fly")
    }

}

fun main() {
    
    
    Animal.fly()
    Animal.eat()
}

kotlin 中的对象声明被编译成了通过静态字段来持有它的单一实例的类,这个字段名字始终都是 INSTANCE

例如,对于 kotlin 中的如下两个对象声明

class Test {
    
    

    object SingleClass {
    
    
        val names = arrayListOf<String>()
    }

    object SingleClass2 {
    
    
        val names = arrayListOf<String>()
    }

}

在 Java 代码中来访问这两个对象

public static void main(String[] args) {
    
    
    Test.SingleClass.INSTANCE.getNames();
    Test.SingleClass2.INSTANCE.getNames();
}

2、伴生对象

如果需要一个可以在没有类实例的情况下调用但是需要访问类内部的函数(类似于 Java 中的静态变量/静态函数),可以将其写成那个类中的对象声明的成员

通过关键字 companion ,就可以获得通过容器类名称来访问这个对象的方法和属性的能力,不再需要显式地指明对象的名称

class Test {
    
    

    companion object {
    
    

        const val NAME = ""

        fun testFun() {
    
    

        }
    }

}

fun main() {
    
    
    Test.NAME
    Test.testFun()
}

1、工厂模式

可以利用伴生对象来实现工厂模式

private class User private constructor(val name: String) {
    
    

    companion object {
    
    
        fun newById(id: Int) = User(id.toString())

        fun newByDouble(double: Double) = User(double.toString())
    }

}

fun main() {
    
    
    //构造函数私有,无法创建
    //val user1 = User("leavesC")
    val user2 = User.newById(10)
    val user3 = User.newByDouble(1.3)
}

2、指定名称

伴生对象既可以为其指定名字,也可以直接使用其默认名 Companion,在引用伴生对象时,可以自由选择是否要在类名后加上伴生对象名

如果使用的是其默认名 Companion(没有自定义名称),则以下两种引用方式都是等价的

val user2 = User.Companion.newById(10)
val user3 = User.newByDouble(1.3)

如果为伴生对象声明了自定义名称,引用方式等同

private class User private constructor(val name: String) {
    
    

    companion object UserLoader {
    
    
        fun newById(id: Int) = User(id.toString())

        fun newByDouble(double: Double) = User(double.toString())
    }

}

fun main() {
    
    
    //构造函数私有,无法创建
    //val user1 = User("leavesC")
    val user2 = User.UserLoader.newById(10)
    val user3 = User.newByDouble(1.3)
}

3、实现接口

伴生对象也可以实现接口,且可以直接将包含它的类的名字当做实现了该接口的对象实例来使用

private class User private constructor(val name: String) {
    
    

    companion object UserLoader : Runnable {
    
    

        override fun run() {
    
    

        }
    }

}

fun newThread(runnable: Runnable) = Thread(runnable)

fun main() {
    
    
    //User 会直接被当做 Runnable 的实例
    val thread = newThread(User)
    val thread2 = newThread(User.UserLoader)
}

3、对象表达式

object 能用来声明匿名对象,可用于替代 Java 中的匿名内部类,且对象表达式中的代码可以访问并修改其外部的非 final 型的变量

fun newThread(runnable: Runnable) = Thread(runnable)

fun main() {
    
    
    var count = 0
    val thread = newThread(object : Runnable {
    
    
        override fun run() {
    
    
            count++
        }
    })
}

二十四、委托

1、委托模式

委托模式是一种基本的设计模式,该模式下有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。kotlin 原生支持委托模式,可以零样板代码来实现,通过关键字 by 实现委托

interface Printer {
    
    

    fun print()
    
}

class DefaultPrinter : Printer {
    
    

    override fun print() {
    
    
         println("DefaultPrinter print")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer

fun main() {
    
    
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter print
}

CustomPrinter 的 by 子句表示将会在 CustomPrinter 中存储 printer 变量,并且编译器将为 CustomPrinter 隐式生成 Printer 接口的所有抽象方法,并将这些方法的调用操作转发给 printer

此外,CustomPrinter 也可以决定自己实现部分方法或全部自己实现,但重写的成员不会在委托对象的成员中调用 ,委托对象的成员只能访问其自身对接口成员实现

interface Printer {
    
    

    val message: String

    fun print()

    fun reprint()

}

class DefaultPrinter : Printer {
    
    

    override val message: String = "DefaultPrinter message"

    override fun print() {
    
    
        println(message)
    }

    override fun reprint() {
    
    
        println("DefaultPrinter reprint")
    }

}

class CustomPrinter(val printer: Printer) : Printer by printer {
    
    

    override val message: String = "CustomPrinter message"

    override fun reprint() {
    
    
        println("CustomPrinter reprint")
    }

}

fun main() {
    
    
    val printer = CustomPrinter(DefaultPrinter())
    printer.print() //DefaultPrinter message
    printer.reprint() //CustomPrinter reprint
}

2、属性委托

kotlin 支持通过委托属性将对一个属性的访问操作委托给另外一个对象来完成,对应的语法格式是:

val/var <属性名>: <类型> by <表达式>

属性的委托不必实现任何的接口,但需要提供一个 getValue() 方法与 setValue()(对于 var 属性),对一个属性的 get 和 set 操作会被委托给属性的委托的这两个方法

class Delegate {
    
    
    //第一个参数表示被委托的对象、第二个参数表示被委托对象自身的描述
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    
    
    }
	//第一个参数表示被委托的对象、第二个参数表示被委托对象自身的描述,第三个参数是将要赋予的值
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    
    
    }
}

看以下的小例子,通过输出值就可以看出各个方法的调用时机

package test

import kotlin.reflect.KProperty

class Delegate {
    
    

    private var message: String? = null

    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
    
    
        println("${
      
      thisRef?.javaClass?.name}, thank you for delegating '${
      
      property.name}' to me!")
        return message ?: "null value"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
    
    
        println("$value has been assigned to '${
      
      property.name}' in ${
      
      thisRef?.javaClass?.name}.")
        message = value
    }
}

class Example {
    
    
    var strValue: String by Delegate()
}

fun main() {
    
    
    val example = Example()
    println(example.strValue)
    example.strValue = "leaveC"
    println(example.strValue)
//    test.Example, thank you for delegating 'strValue' to me!
//    null value
//    leaveC has been assigned to 'strValue' in test.Example.
//    test.Example, thank you for delegating 'strValue' to me!
//    leaveC
}

3、延迟属性

lazy() 是接受一个 lambda 并返回一个 Lazy < T > 实例的函数,返回的实例可以作为实现延迟属性的委托,第一次调用 get() 会执行已传递给 lazy() 函数的 lambda 表达式并记录结果, 后续调用 get() 只是返回记录的结果

class Example {
    
    

    val lazyValue1: String by lazy {
    
    
        println("lazyValue1 computed!")
        "Hello"
    }

    val lazyValue2: String by lazy(LazyThreadSafetyMode.SYNCHRONIZED) {
    
    
        println("lazyValue2 computed!")
        computeLazyValue()
    }

    private fun computeLazyValue() = "leavesC"

}

fun main() {
    
    
    val example = Example()
    println(example.lazyValue1) //lazyValue1 computed!     Hello
    println(example.lazyValue1) //Hello
    println(example.lazyValue2) //lazyValue2 computed! leavesC
}

默认情况下,对于 lazy 属性的求值是带同步锁的(synchronized),即带有 LazyThreadSafetyMode.SYNCHRONIZED 参数,此时该值只允许同一时刻只能有一个线程对其进行初始化,并且所有线程会看到相同的初始化值。如果初始化委托的同步锁不是必需的,即如果允许多个线程同时执行,那么可以将 LazyThreadSafetyMode.PUBLICATION 作为参数传递给 lazy() 函数。 而如果你确定初始化将总是发生在单个线程,那么可以使用 LazyThreadSafetyMode.NONE 模式, 此时不会有任何线程安全的保证以及相关的资源开销

4、可观察属性

Delegates.observable() 接受两个参数:初始值以及修改属性值时的回调函数。当为属性赋值后就会调用该回调函数,该回调函数包含三个参数:被赋值的属性、旧值与新值

fun main() {
    
    
    val example = Example()
    example.age = 24 //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = 27 //kProperty.name: age , oldValue: 24 , newValue: 27
}

class Example {
    
    
    var age: Int by Delegates.observable(-100) {
    
     kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${
      
      kProperty.name} , oldValue: $oldValue , newValue: $newValue")
    }
}

如果想要拦截一个赋值操作并判断是否进行否决,可以使用 vetoable() 函数,通过返回一个布尔值来决定是否进行拦截,该判断逻辑是在属性被赋新值生效之前进行

fun main() {
    
    
    val example = Example()
    example.age = 24  //kProperty.name: age , oldValue: -100 , newValue: 24
    example.age = -10 //kProperty.name: age , oldValue: 24 , newValue: -10
    example.age = 30  //kProperty.name: age , oldValue: 24 , newValue: 30 (oldValue 依然是 24,说明第二次的赋值操作被否决了)
}

class Example {
    
    
    var age: Int by Delegates.vetoable(-100) {
    
     kProperty: KProperty<*>, oldValue: Int, newValue: Int ->
        println("kProperty.name: ${
      
      kProperty.name} , oldValue: $oldValue , newValue: $newValue")
        age <= 0 //返回true 则表示拦截该赋值操作
    }
}

5、把属性储存在映射中

可以在一个 map 映射里存储属性的值,然后把属性的存取操作委托给 map 进行管理

fun main() {
    
    
    val student = Student(
        mapOf(
            "name" to "leavesCZY",
            "age" to 24
        )
    )
    println(student.name)
    println(student.age)
}

class Student(val map: Map<String, Any?>) {
    
    
    val name: String by map
    val age: Int by map
}

在上述示例中,属性 name 和 age 都是不可变的(val),因此 map 的类型也是 Map 而非 MutableMap(MutableMap 在赋值后可以修改),因此如果为了支持 var 属性,可以将只读的 Map 换成 MutableMap

6、局部委托属性

可以将局部变量声明为委托属性

class Printer {
    
    

    fun print() {
    
    
        println("temp.Printer print")
    }

}

fun getPrinter(): Printer {
    
    
    println("temp.Printer getPrinter")
    return Printer()
}

//局部委托
fun example(getPrinter: () -> Printer) {
    
    
    val lPrinter by lazy(getPrinter)
    val valid = true
    if (valid) {
    
    
        lPrinter.print()
    }
}

fun main() {
    
    
    example {
    
     getPrinter() }
    //temp.Printer getPrinter
    //temp.Printer print
}

委托变量只会在第一次访问时才会进行初始化,因此如果 valid 为 false 的话,getPrinter() 方法就不会被调用

二十五、注解

注解是将元数据附加到代码元素上的一种方式,附件的元数据就可以在编译后的类文件或者运行时被相关的源代码工具访问

注解的语法格式如下所示:

annotation class AnnotationName()

注解的附加属性可以通过用元注解标注注解类来指定:

  • @Target 指定该注解标注的允许范围(类、函数、属性等)
  • @Retention 指定该注解是否要存储在编译后的 class 文件中,如果要保存,则在运行时可以通过反射来获取到该注解值
  • @Repeatable 标明允许在单个元素上多次使用相同的该注解
  • @MustBeDocumented 指定该注解是公有 API 的一部分,并且应该包含在生成的 API 文档中显示的类或方法的签名中
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
@MustBeDocumented
annotation class AnnotationName()

注解可以声明包含有参数的构造函数

annotation class OnClick(val viewId: Long)

允许的参数类型有:

  • 原生数据类型,对应 Java 原生的 int 、long、char 等
  • 字符串
  • class 对象
  • 枚举
  • 其他注解
  • 以上类型的数组

注解参数不能包含有可空类型,因为 JVM 不支持将 null 作为注解属性的值来存储

看一个在运行时获取注解值的例子

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
annotation class OnClick(val viewId: Long)

class AnnotationsTest {
    
    

    @OnClick(200300)
    fun onClickButton() {
    
    
        println("Clicked")
    }

}

fun main() {
    
    
    val annotationsTest = AnnotationsTest()
    for (method in annotationsTest.javaClass.methods) {
    
    
        for (annotation in method.annotations) {
    
    
            if (annotation is OnClick) {
    
    
                println("method name: " + method.name)  //method name: onClickButton
                println("OnClick viewId: " + annotation.viewId)  //OnClick viewId: 200300
            }
        }
    }
}

Guess you like

Origin blog.csdn.net/gqg_guan/article/details/131992722