构造函数、继承

// 类继承使用`:`声明 open class Shape // 该类开放继承 class TestClass(var height: Double, var length: Double): Shape() { // 类声明中所列参数的默认构造函数会自动可用 var s = height * length } // 在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。主构造函数是类头的一部分 class Person constructor(firstName: String) { /*……*/ } class Person(firstName: String) { /*……*/ } // 次构造函数 // 如果类有一个主构造函数or init(因为初始化代码块实际会成为主构造函数的一部分),每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可 class Person(val name: String) { val children: MutableList<Person> = mutableListOf() constructor(name: String, parent: Person) : this(name) { parent.children.add(this) } }

伴生对象

虽然您可以只创建一个名为“letter”的常量,但当您向应用添加更多 intent extra 时,代码可能会变得比较庞杂。另外,您应该将此常量放入哪个类呢?请记住,该字符串会同时用于 DetailActivity 和 MainActivity。您需要一种方法来定义常量,使其能在多个类中使用,同时保持代码的条理性。
  • Kotlin 中有一种便捷的功能叫伴生对象,可用来分离常量,使它们无需特定类实例即可使用。伴生对象与其他对象类似,例如某个类的实例。但是,在程序使用期间,只会有一个伴生对象实例存在,正因为如此,这有时被称为单例模式
// DetailActivity定义和使用伴生对象 class DetailActivity : AppCompatActivity() { companion object { const val LETTER = "letter" } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... val letterId = intent?.extras?.getString(LETTER).toString() // ... // LetterAdapter使用 intent.putExtra(DetailActivity.LETTER, holder.button.text.toString()) // 传入一段数据

属性委托

在 Kotlin 中,每个可变 (var) 属性都具有自动为其生成的默认 getter 和 setter 函数。当您为该属性赋值或读取其值时,系统会调用 setter 和 getter 函数。
只读属性 (val) 与可变属性略有不同,默认情况下仅为其生成 getter 函数。当您读取只读属性的值时,系统会调用此 getter 函数。
Kotlin 中的属性委托可以帮助您将 getter-setter 的责任移交给另一个类。
此类(称为“委托类”)提供属性的 getter 和 setter 函数并处理其变更。
delegate 属性使用 by 子句和 delegate 类实例进行定义:
// Syntax for property delegation var <property-name> : <property-type> by <delegate-class>()
  • 在Android使用属性委托
    • 那么,当设备经过配置变更后,应用会丢失 viewModel 引用的状态。例如,如果您旋转设备,activity 就会被销毁并重新创建,而您将重新获得初始状态的新视图模型实例。

幕后字段

在 Kotlin 中,字段仅作为属性的一部分在内存中保存其值时使用。字段不能直接声明。 然而,当一个属性需要一个幕后字段时,Kotlin 会自动提供。这个幕后字段可以使用 field  标识符在访问器中引用
var counter = 0 // 这个初始器直接为幕后字段赋值 set(value) { if (value >= 0) field = value }

后备属性

使用后备属性,可以从 getter 返回确切对象之外的某些其他内容。
我们已经学过,Kotlin 框架会为每个属性生成 getter 和 setter。
对于 getter 和 setter 方法,您可以替换其中一个方法或同时替换两个方法,并提供您自己的自定义行为。为了实现后备属性,您需要替换 getter 方法以返回只读版本的数据。
// Declare private mutable variable that can only be modified // within the class it is declared. private var _count = 0 // Declare another public immutable field and override its getter method. // Return the private property's value in the getter method. // When count is accessed, the get() function is called and // the value of _count is returned. val count: Int get() = _count
举例而言,在您的应用中,您需要应用数据仅对 ViewModel 可见:
在 ViewModel 类之内:
  • _count 属性设为 private 且可变。因此,只能在 ViewModel 类中对其进行访问和修改。惯例是为 private 属性添加下划线前缀。
在 ViewModel 类之外:
  • Kotlin 中的默认可见性修饰符为 public,因此 count 是公共属性,可从界面控制器等其他类对其进行访问。由于只有 get() 方法会被替换,所以此属性不可变且为只读状态。当外部类访问此属性时,它会返回 _count 的值且其值无法修改。这可以防止外部类擅自对 ViewModel 内的应用数据进行不安全的更改,但允许外部调用方安全地访问该应用数据的值。

延迟初始化

通常,在声明变量时,您会预先为其提供一个初始值。不过,如果您还没准备好赋值,也可以稍后再对其进行初始化。如需在 Kotlin 中对属性进行延迟初始化,请使用关键字 lateinit(意思是延迟初始化)。如果您能保证您会在使用前对属性进行初始化,就可以使用 lateinit 声明该属性。在对变量进行初始化之前,不会为其分配内存。如果您尝试在初始化变量之前对其进行访问,应用会崩溃。
// 检测是否已经初始化 if (foo::bar.isInitialized) { println(foo.bar) }

接口

接口

interface MyInterface { val prop: Int // 抽象的 val propertyWithImplementation: String get() = "foo" fun foo() { print(prop) } } // 解决实现两个接口的冲突,A、B都有函数foo() class D : A, B { override fun foo() { super<A>.foo() super<B>.foo() } }

函数式(SAM)接口

只有一个抽象方法的接口称为函数式接口 或 单一抽象方法(SAM)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
fun interface KRunnable { fun invoke() }
  • SAM转换(Single Abstract Method Conversions):对于只有单个非默认抽象方法接口的转换
例如,有这样一个 Kotlin 函数式接口:
fun interface IntPredicate { fun accept(i: Int): Boolean }
如果不使用 SAM 转换,那么你需要像这样编写代码:
// 创建一个类的实例 val isEven = object : IntPredicate { override fun accept(i: Int): Boolean { return i % 2 == 0 } }
通过利用 Kotlin 的 SAM 转换,可以改为以下等效代码:
// 通过 lambda 表达式创建一个实例 val isEven = IntPredicate { it % 2 == 0 }

扩展

Kotlin 能够对一个类扩展新功能而无需继承该类或者使用像 装饰者 这样的设计模式。 这通过做扩展 的特殊声明完成。
说白了就是不继承或者修改原类,给类添加扩展函数或扩展属性
  • 扩展函数
    • 声明一个扩展函数需用一个接收者类型也就是被扩展的类型来作为他的前缀。 下面代码为 MutableList<Int> 添加一个swap 函数:
      fun MutableList<Int>.swap(index1: Int, index2: Int) { val tmp = this[index1] // “this”对应该列表 this[index1] = this[index2] this[index2] = tmp } // MutableList<Int> 就是接收者 val list = mutableListOf(1, 2, 3) list.swap(0, 2) // “swap()”内部的“this”会保存“list”的值
    • 扩展是静态解析的
      • 扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的
        fun main() { open class Shape class Rectangle: Shape() fun Shape.getName() = "Shape" fun Rectangle.getName() = "Rectangle" fun printClassName(s: Shape) { println(s.getName()) } printClassName(Rectangle()) // 打印'Shape',因为printClassName中接收的参数声明的是`Shape`类 }
    • 成员函数与扩展函数有相同的接收者类型、名字,且都适用给定的参数,这种情况下总是取成员函数
      • fun main() { class Example { fun printFuncType() { println("Class method") } } fun Example.printFuncType() { println("Extension func") } Example().printFuncType() // print Class method }
      • 扩展函数可以重载成员函数
        • fun main() { class Example { fun printFuncType() { println("Class method") } } fun Example.printFuncType(i: Int) { println("Extension func #$i") } Example().printFuncType(1) // print Extension func #1 }
    • 可空接收者
      • fun Any?.toString(): String { if (this == null) return "null" // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString() // 解析为 Any 类的成员函数 return toString() }
  • 扩展属性
    • val <T> List<T>.lastIndex: Int get() = size - 1
    • 由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getter/setter 定义。
      • val House.number = 1 // 错误:扩展属性不能有初始化器
  • 伴生对象的扩展
class MyClass { companion object { } // 将被称为 "Companion" } fun MyClass.Companion.printCompanion() { println("companion") } fun main() { MyClass.printCompanion() }
  • 扩展作用域
    • 大多数情况都在顶层定义扩展 — 直接在包里
    • package org.example.declarations fun List<String>.getLongestString() { /*……*/}
    • 如需使用在包之外的一个扩展,只需在调用方导入它
    • package org.example.usage import org.example.declarations.getLongestString fun main() { val list = listOf("red", "green", "blue") list.getLongestString() }
  • 扩展声明为成员
    • 可以在一个类内部为另一个类声明扩展。在这样的扩展内部,有多个隐式接收者—— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为分发接收者,扩展方法调用所在的接收者类型的实例称为扩展接收者
      class Host(val hostname: String) { fun printHostname() { print(hostname) } } class Connection(val host: Host, val port: Int) { fun printPort() { print(port) } fun Host.printConnectionString() { printHostname() // 调用 Host.printHostname() print(":") printPort() // 调用 Connection.printPort() } fun connect() { /*……*/ host.printConnectionString() // 调用扩展函数 } } fun main() { Connection(Host("kotl.in"), 443).connect() //Host("kotl.in").printConnectionString() // 错误,该扩展函数在 Connection 外不可用 }

数据类

密封类

嵌套类

枚举类

内联类

对象表达式

类型别名

线程

fun main() { val thread = Thread { println("${Thread.currentThread()} has run") } thread.start() }

协程

协程能够处理多任务,但比直接使用线程更为抽象。协程的一项重要功能是能够存储状态,以便协程可以暂停和恢复。协程可以执行,也可以不执行。
作业
表示可取消的工作单元,例如使用 launch() 函数创建的工作单元。
用于创建新协程的函数,例如 launch() 和 async() 对 CoroutineScope 进行扩展。
确定协程将使用的线程。Main 调度程序将始终在主线程上运行协程,而 DefaultIO 或 Unconfined 等调度程序则会使用其他线程。
  • launch()
// launch() 完整签名 fun CoroutineScope.launch { context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit } /* * 在后台,您传递给 launch 函数的代码块会使用 suspend 关键字进行标记。 * Suspend 表示代码块或函数可以暂停或恢复。 */
eg:
import kotlinx.coroutines.* // launch() 函数会根据括起来的代码(封装在可取消作业对象中)创建协程。launch() 用于无需在协程范围之外返回值的情况。 fun main() { repeat(3) { GlobalScope.launch { println("Hi from ${Thread.currentThread()}") } } }
  • runBlocking()
    • 启动新协程并在新协程完成之前阻塞当前线程。它主要用于在主要函数和测试中的阻塞代码和非阻塞代码之间架起桥梁。该函数在典型的 Android 代码中并不常用。
      感觉有点类似把异步搞成同步?
      eg:
      import kotlinx.coroutines.* import java.time.LocalDateTime import java.time.format.DateTimeFormatter val formatter = DateTimeFormatter.ISO_LOCAL_TIME val time = { formatter.format(LocalDateTime.now()) } suspend fun getValue() {} fun main() { runBlocking { val num1 = getValue() val num2 = getValue() println("result of num1 + num2 is ${num1 + num2}") } }
  • async()
    • Kotlin 的 async 函数与 launch 类似。
      Fun CoroutineScope.async() { context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T }: Deferred<T> /* * async() 函数会返回 Deferred 类型的值。 * Deferred 是一个可取消的 Job,可以存储对未来值的引用。 * 使用 Deferred,您仍然可以调用函数,就像它会立即返回一个值一样 - * Deferred 只充当占位符,因为您无法确定异步任务将何时返回。 * Deferred(在其他语言中也称为 promise 或 future)能够保证稍后会向此对象返回一个值。 * 另一方面,异步任务默认不会阻塞或等待执行。 * 若要启动异步任务,当前的代码行需要等待 Deferred 的输出,您可以对其调用 await()。它将会返回原始值。 */
  • suspend
    • // getValue()调用一个suspend函数,所以getValue()也是suspend函数 suspend fun getValue(): Double { println("entering getValue() at ${time()}") delay(3000) // delay()是一个suspend函数 println("leaving getValue() at ${time()}") return Math.random() }

Else

可见性修饰符

类、对象、接口、构造函数、方法与属性及其 setter 都可以有可见性修饰符private、 protected、 internal和 public。 默认可见性是 public
    • 如果你不使用任何可见性修饰符,默认为 public,这意味着你的声明将随处可见。
    • 如果你声明为 private,它只会在声明它的文件内可见。
    • 如果你声明为 internal,它会在相同模块内随处可见。
    • protected 修饰符不适用于顶层声明。
    • private 意味着只该成员在这个类内部(包含其所有成员)可见;
    • protected 意味着该成员具有与 private 一样的可见性,但也在子类中可见。
    • internal 意味着能见到类声明的本模块内的任何客户端都可见其 internal 成员。
    • public 移位置能见到类声明的任何客户端都可见其 public 成员。
  • 构造函数
    • class C private constructor(a: Int) { …… }
  • 局部声明
    • 局部变量、函数和类不能有可见性修饰符。
  • 模块
    • 可见性修饰符 internal意味着该成员只在相同模块内可见

Elvis 运算符 (?:)

表示如果左侧的表达式不为 null,则使用该表达式。但如果左侧的表达式为 null,请使用 Elvis 运算符右侧的表达式。
val l = b.length ?: -1

Lambda表达式

// code episode 01 view.setOnClickListener{ println("click") } // code episode 02 view.setOnClickListener({ v -> println("click") })
code episode 01是02的简写形式,基于两点特性:
  1. 如果 lambda 是一个函数的唯一参数,那么调用这个函数时可以省略圆括号
  1. 如果 lambda 所表示的匿名函数只有一个参数,那么可以省略它的声明以及->符号(默认会用it来给省略的参数名命名)
  • 尾随lambda语法
    • 当传入的最后一个参数是函数时,您可以将 lambda 表达式放在圆括号外。
      // ...接受两个参数,第二个为函数,则可以把lambda表达式放在圆括号外 .setPositiveButton(getString(R.string.play_again)) { _, _ -> restartGame() }

高阶函数

fun num1AndNum2(num1: Int, num2: Int, operation: (Int) -> Int): Int { val result = operation(num1, num2) return result } fun plus(num1: Int, num2: Int): Int { return num1 + num2 } fun main() { // ... val result = num1AndNum2(num1, num2, ::plus) // ::plus为传函数引用,将plus()函数传入高阶函数 // ... val result2 = num1AndNum2(num1, num2) { n1, n2 -> n1 - n2 } }

Else

    /* 在这里,`get()` 表示此属性仅供获取。也就是说,您可以**获取**该值,但是,该值一旦进行分配之后,就不能再分配给其他属性。 在 Kotlin 以及一般的编程语言中,您通常会遇到以下划线开头的属性名称。这通常表示属性不应直接访问。 */ private val binding get() = _binding!!
    1. 安全操作值的函数
    // +1 _currentWordCount.value = (_currentWordCount.value)?.inc() // + _score.value = (_score.value)?.plus(SCORE_INCREASE)
    1. apply: apply 是 Kotlin 标准库中的作用域函数。它在对象的上下文中执行代码块。它会形成一个临时作用域,在该作用域中,您可以访问对象而不需要其名称。apply 的常见用例是配置对象。此类调用可以解读为“对对象应用以下赋值”。
    clark.apply { firstName = "Clark" lastName = "James" age = 18 } // The equivalent code without apply scope function would look like the following. clark.firstName = "Clark" clark.lastName = "James" clark.age = 18
    1. repeat()
    repeat(3) { println("repeat") } // repeat\n repeat\n repeat\n
    1. 范围
    val diceRange = 1..6 val diceRange: IntRange = 1..6 val randomNumebr = diceRange.random()
    badge