// 类继承使用`:`声明 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
。您需要一种方法来定义常量,使其能在多个类中使用,同时保持代码的条理性。
// 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()) // 传入一段数据
var
) 属性都具有自动为其生成的默认 getter 和 setter 函数。当您为该属性赋值或读取其值时,系统会调用 setter 和 getter 函数。val
) 与可变属性略有不同,默认情况下仅为其生成 getter 函数。当您读取只读属性的值时,系统会调用此 getter 函数。by
子句和 delegate 类实例进行定义:// Syntax for property delegation var <property-name> : <property-type> by <delegate-class>()
那么,当设备经过配置变更后,应用会丢失 viewModel
引用的状态。例如,如果您旋转设备,activity 就会被销毁并重新创建,而您将重新获得初始状态的新视图模型实例。
// 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
类之外:public
,因此 count
是公共属性,可从界面控制器等其他类对其进行访问。由于只有 get()
方法会被替换,所以此属性不可变且为只读状态。当外部类访问此属性时,它会返回 _count
的值且其值无法修改。这可以防止外部类擅自对 ViewModel
内的应用数据进行不安全的更改,但允许外部调用方安全地访问该应用数据的值。只有一个抽象方法的接口称为函数式接口 或 单一抽象方法(SAM)接口。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。
fun interface KRunnable { fun invoke() }
fun interface IntPredicate { fun accept(i: Int): Boolean }
// 创建一个类的实例 val isEven = object : IntPredicate { override fun accept(i: Int): Boolean { return i % 2 == 0 } }
// 通过 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
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 外不可用 }
协程能够处理多任务,但比直接使用线程更为抽象。协程的一项重要功能是能够存储状态,以便协程可以暂停和恢复。协程可以执行,也可以不执行。
launch()
和 async()
对 CoroutineScope
进行扩展。Main
调度程序将始终在主线程上运行协程,而 Default
、IO
或 Unconfined
等调度程序则会使用其他线程。// launch() 完整签名 fun CoroutineScope.launch { context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> Unit } /* * 在后台,您传递给 launch 函数的代码块会使用 suspend 关键字进行标记。 * Suspend 表示代码块或函数可以暂停或恢复。 */
import kotlinx.coroutines.* // launch() 函数会根据括起来的代码(封装在可取消作业对象中)创建协程。launch() 用于无需在协程范围之外返回值的情况。 fun main() { repeat(3) { GlobalScope.launch { println("Hi from ${Thread.currentThread()}") } } }
启动新协程并在新协程完成之前阻塞当前线程。它主要用于在主要函数和测试中的阻塞代码和非阻塞代码之间架起桥梁。该函数在典型的 Android 代码中并不常用。
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}") } }
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()。它将会返回原始值。 */
// 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() }
类、对象、接口、构造函数、方法与属性及其 setter 都可以有可见性修饰符。private
、protected
、internal
和public
。 默认可见性是public
public
,这意味着你的声明将随处可见。private
,它只会在声明它的文件内可见。internal
,它会在相同模块内随处可见。protected
修饰符不适用于顶层声明。private
意味着只该成员在这个类内部(包含其所有成员)可见;protected
意味着该成员具有与 private
一样的可见性,但也在子类中可见。internal
意味着能见到类声明的本模块内的任何客户端都可见其 internal
成员。public
移位置能见到类声明的任何客户端都可见其 public
成员。class C private constructor(a: Int) { …… }
internal
意味着该成员只在相同模块内可见val l = b.length ?: -1
// code episode 01 view.setOnClickListener{ println("click") } // code episode 02 view.setOnClickListener({ v -> println("click") })
->
符号(默认会用it
来给省略的参数名命名)当传入的最后一个参数是函数时,您可以将 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 } }
/* 在这里,`get()` 表示此属性仅供获取。也就是说,您可以**获取**该值,但是,该值一旦进行分配之后,就不能再分配给其他属性。 在 Kotlin 以及一般的编程语言中,您通常会遇到以下划线开头的属性名称。这通常表示属性不应直接访问。 */ private val binding get() = _binding!!
// +1 _currentWordCount.value = (_currentWordCount.value)?.inc() // + _score.value = (_score.value)?.plus(SCORE_INCREASE)
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
repeat(3) { println("repeat") } // repeat\n repeat\n repeat\n
val diceRange = 1..6 val diceRange: IntRange = 1..6 val randomNumebr = diceRange.random()