导图社区 Kotlin
这是一篇关于Kotlin的思维导图,主要内容包括:基础语法,基本数据类型,条件控制,循环控制,类和对象,继承,接口,扩展,数据类与密封类,泛型,枚举类,对象表达式/声明,委托。
编辑于2026-01-06 14:43:37Kotlin
基础语法
hello word
fun main(args: Array<String>){ println("Hello Word") } //Hello Word
包声明
package com.runoob.main
函数定义
函数定义使用关键字 fun,参数格式为:参数 : 类型
有返回值
fun sum(a: Int, b: Int): Int { // Int 参数,返回值 Int return a + b } 表达式作为函数体,返回类型自动推断: fun sum(a: Int, b: Int) = a + b public fun sum(a: Int, b: Int): Int = a + b // public 方法则必须明确写出返回类型
无返回值
fun printSum(a: Int, b: Int): Unit { print(a + b) } // 如果是返回 Unit类型,则可以省略(对于public方法也是这样): public fun printSum(a: Int, b: Int) { print(a + b) }
可变长参数函数
fun vars(vararg v:Int){ for(vt in v){ print(vt) } } // 测试 fun main(args: Array<String>) { vars(1,2,3,4,5) // 输出12345 }
vararg 关键字
lambda(匿名函数)
// 测试 fun main(args: Array<String>) { val sumLambda: (Int, Int) -> Int = {x,y -> x+y} println(sumLambda(1,2)) // 输出 3 }
定义常量与变量
可变变量定义:var 关键字
var <标识符> : <类型> = <初始化值>
不可变变量定义:val 关键字,只能赋值一次的变量(类似Java中final修饰的变量)
val <标识符> : <类型> = <初始化值>
字符串模板
fun main() { var a = 1 // 模板中的简单名称: val s1 = "a is $a" a = 2 // 模板中的任意表达式: val s2 = "${s1.replace("is", "was")}, but now is $a" println(s1) println(s2) } //a is 1 //a was 1, but now is 2
$ 表示一个变量名或者变量值
$varName 表示变量值
${varName.fun()} 表示变量的方法返回值:
NULL检查机制
//类型后面加?表示可为空 var age: String? = "23" //抛出空指针异常 val ages = age!!.toInt() //不做处理返回 null val ages1 = age?.toInt() //age为空返回-1 val ages2 = age?.toInt() ?: -1
对于声明可为空的参数,在使用时要进行空判断处理
// 定义函数 parseInt, 接收一个字符串并尝试将其转换为整数,如果失败返回 null fun parseInt(str: String): Int? { // 使用 toIntOrNull 将字符串转换为整数,失败时返回 null return str.toIntOrNull() } // 定义函数 printProduct, 接收两个字符串参数,尝试将它们转换为整数并输出乘积 fun printProduct(arg1: String, arg2: String) { // 将第一个字符串转换为整数,返回值为 Int?,即可能是 null val x = parseInt(arg1) // 将第二个字符串转换为整数,返回值也可能是 null val y = parseInt(arg2) // 直接使用 `x * y` 会导致错误, 因为它们可能为 null // 如果 x 和 y 都不为 null, 则执行乘法操作 if (x != null && y != null) { // 在进行过 null 值检查之后, x 和 y 的类型会被自动转换为非 null 变量 println(x * y) // 输出 x 和 y 的乘积 } else { // 如果 x 或 y 为 null,则输出提示信息,表明其中一个参数不是数字 println("'$arg1' or '$arg2' is not a number") } } // 主函数 fun main() { // 第一次调用 printProduct,两个参数都是有效数字,应该输出乘积 printProduct("6", "7") // 输出: 42 // 第二次调用,第一个参数不是数字,输出错误提示 printProduct("a", "7") // 输出: 'a' or '7' is not a number // 第三次调用,两个参数都不是数字,输出错误提示 printProduct("a", "b") // 输出: 'a' or 'b' is not a number } //42 //'a' or '7' is not a number //'a' or 'b' is not a number
字段后加!!像Java一样抛出空异常
字段后加?可不做处理返回值为 null 或配合 ?: 做空判断处理
类型检测及自动类型转换
fun getStringLength(obj: Any): Int? { if (obj !is String) return null // 在这个分支中, `obj` 的类型会被自动转换为 `String` return obj.length }
区间
for (i in 1..4) print(i) // 输出“1234” for (i in 4..1) print(i) // 什么都不输出 if (i in 1..10) { // 等同于 1 <= i && i <= 10 println(i) } // 使用 step 指定步长 for (i in 1..4 step 2) print(i) // 输出“13” for (i in 4 downTo 1 step 2) print(i) // 输出“42” // 使用 until 函数排除结束元素 for (i in 1 until 10) { // i in [1, 10) 排除了 10 println(i) }
由具有操作符形式 .. 的 rangeTo 函数辅以 in 和 !in 形成。
fun main(args: Array<String>) { print("循环输出:") for (i in 1..4) print(i) // 输出“1234” println("\n----------------") print("设置步长:") for (i in 1..4 step 2) print(i) // 输出“13” println("\n----------------") print("使用 downTo:") for (i in 4 downTo 1 step 2) print(i) // 输出“42” println("\n----------------") print("使用 until:") // 使用 until 函数排除结束元素 for (i in 1 until 4) { // i in [1, 4) 排除了 4 print(i) } println("\n----------------") } 输出结果: 循环输出:1234 ---------------- 设置步长:13 ---------------- 使用 downTo:42 ---------------- 使用 until:123 ----------------
基本数据类型
不同于 Java 的是,字符不属于数值类型,是一个独立的数据类型。
整数类型
Byte
8 位,范围从 -128 到 127。
Short
16 位,范围从 -32,768 到 32,767。
Int
32 位,范围从 -2^31 到 2^31 - 1。
Long
64 位,范围从 -2^63 到 2^63 - 1。
浮点数类型
Float
32 位,单精度,带有 6-7 位有效数字。
Double
64 位,双精度,带有 15-16 位有效数字。
字符类型
Char
16 位的 Unicode 字符。
布尔类型
Boolean
有两个值:true和false
字符串类型
String
一系列字符的序列。
数组类型
Kotlin 提供了数组类型来存储同种类型的元素,例如:
IntArray
存储Int类型的数组。
DoubleArray
存储Double类型的数组。
Array<T>
泛型数组,可以存储任意类型。
字面常量
十进制:123
长整型以大写的 L 结尾:123L
16 进制以 0x 开头:0x0F
2 进制以 0b 开头:0b00001011
注意:8进制不支持
Doubles 默认写法:123.5,123.5e10
Floats 使用 f 或者 F 后缀:123.5f
类型转换
toByte(): Byte
toShort(): Short
toInt(): Int
toLong(): Long
toFloat(): Float
toDouble(): Double
toChar(): Char
位操作符
shl(bits) – 左移位 (Java’s <<)
shr(bits) – 右移位 (Java’s >>)
ushr(bits) – 无符号右移位 (Java’s >>>)
and(bits) – 与
or(bits) – 或
xor(bits) – 异或
inv() – 反向
字符
和 Java 不一样,Kotlin 中的 Char 不能直接和数字操作,Char 必需是单引号 ' 包含起来的。比如普通字符 '0','a'。
fun decimalDigitValue(c: Char): Int { if (c !in '0'..'9') throw IllegalArgumentException("Out of range") return c.toInt() - '0'.toInt() // 显式转换为数字 }
布尔
|| – 短路逻辑或
&& – 短路逻辑与
! - 逻辑非
数组
fun main(args: Array<String>) { //[1,2,3] val a = arrayOf(1, 2, 3) //[0,2,4] val b = Array(3, { i -> (i * 2) }) //读取数组内容 println(a[0]) // 输出结果:1 println(b[1]) // 输出结果:2 }
数组用类 Array 实现,并且还有一个 size 属性及 get 和 set 方法
数组的创建两种方式:一种是使用函数arrayOf();另外一种是使用工厂函数。
字符串
for (c in str) { println(c) }
和 Java 一样,String 是不可变的。,也可以通过 for 循环来遍历:
fun main(args: Array<String>) { val text = """ 多行字符串 多行字符串 """ println(text) // 输出有一些前置空格 }
String 可以通过 trimMargin() 方法来删除多余的空白。
fun main(args: Array<String>) { //默认 | 用作边界前缀 val text = """ |多行字符串 |菜鸟教程 |多行字符串 |Runoob """.trimMargin() println(text) // 前置空格删除了 }
字符串模板
模板表达式以美元符($)开头
由一个简单的名字构成: fun main(args: Array<String>) { val i = 10 val s = "i = $i" // 求值结果为 "i = 10" println(s) } 或者用花括号扩起来的任意表达式: fun main(args: Array<String>) { val s = "runoob" val str = "$s.length is ${s.length}" // 求值结果为 "runoob.length is 6" println(str) }
条件控制
IF 表达式
// 传统用法 var max = a if (a < b) max = b // 使用 else var max: Int if (a > b) { max = a } else { max = b } // 作为表达式 val max = if (a > b) a else b
把 IF 表达式的结果赋值给一个变量。
val max = if (a > b) { print("Choose a") a } else { print("Choose b") b }
val c = if (condition) a else b
使用区间
fun main(args: Array<String>) { val x = 5 val y = 9 if (x in 1..8) { println("x 在区间内") } } 输出结果为: x 在区间内
When 表达式
fun main(args: Array<String>) { var x = 0 when (x) { 0, 1 -> println("x == 0 or x == 1") else -> println("otherwise") } when (x) { 1 -> println("x == 1") 2 -> println("x == 2") else -> { // 注意这个块 println("x 不是 1 ,也不是 2") } } when (x) { in 0..10 -> println("x 在该区间范围内") else -> println("x 不在该区间范围内") } } //x == 0 or x == 1 //x 不是 1 ,也不是 2 //x 在该区间范围内
将它的参数和所有的分支条件顺序比较,直到某个分支满足条件。类似其他语言的 switch 操作符
循环控制
For 循环
对集合进行迭代: fun main(args: Array<String>) { val items = listOf("apple", "banana", "kiwi") for (item in items) { println(item) } for (index in items.indices) { println("item at $index is ${items[index]}") } } 输出结果: apple banana kiwi item at 0 is apple item at 1 is banana item at 2 is kiwi
for 循环可以对任何提供迭代器(iterator)的对象进行遍历
while 与 do...while 循环
fun main(args: Array<String>) { println("----while 使用-----") var x = 5 while (x > 0) { println( x--) } println("----do...while 使用-----") var y = 5 do { println(y--) } while(y>0) } 输出结果: ----while 使用----- 5 4 3 2 1 ----do...while 使用----- 5 4 3 2 1
返回和跳转
return
默认从最直接包围它的函数或者匿名函数返回。
break
终止最直接包围它的循环。
continue
继续下一次最直接包围它的循环。
类和对象
类定义
class Runoob { // 类名为 Runoob // 大括号内是类体构成 }
Kotlin 中使用关键字 class 声明类,后面紧跟类名
类的属性
class Runoob { var name: String = …… var url: String = …… var city: String = …… }
可以用关键字 var 声明为可变的,否则使用只读关键字 val 声明为不可变
import java.util.* class Person { var lastName: String = "zhang" get() = field.uppercase(Locale.getDefault()) // 将变量赋值后转换为大写 set var no: Int = 100 get() = field // 后端变量 set(value) { if (value < 10) { // 如果传入的值小于 10 返回该值 field = value } else { field = -1 // 如果传入的值大于等于 10 返回 -1 } } var heiht: Float = 145.4f private set } fun main(args: Array<String>) { var person: Person = Person() person.lastName = "wang" println("lastName:${person.lastName}") person.no = 9 println("no:${person.no}") person.no = 20 println("no:${person.no}") } //lastName:WANG //no:9 //no:-1
主构造器
class Person(val name: String, val age: Int) { init { println("Person created: $name, $age") } }
通常在类定义的同时声明。它的特点是简洁并且适合用于初始化类的成员变量。
init
用于包含需要在构造器中执行的初始化逻辑。
次构造器
class Person { var name: String var age: Int constructor(name: String) { this.name = name this.age = 0 // 默认年龄 } constructor(name: String, age: Int) { this.name = name this.age = age } }
是类的额外构造器,它允许你在类中定义多个构造器来支持不同的初始化方式。
constructor
抽象类
abstract
open class Base { open fun f() {} } abstract class Derived : Base() { override abstract fun f() }
嵌套类
class Outer { // 外部类 private val bar: Int = 1 class Nested { // 嵌套类 fun foo() = 2 } } fun main(args: Array<String>) { val demo = Outer.Nested().foo() // 调用格式:外部类.嵌套类.嵌套类方法/属性 println(demo) // == 2 }
可以把类嵌套在其他类中
内部类
class Outer { private val bar: Int = 1 var v = "成员属性" /**嵌套内部类**/ inner class Inner { fun foo() = bar // 访问外部类成员 fun innerTest() { var o = this@Outer //获取外部类的成员变量 println("内部类可以引用外部类的成员,例如:" + o.v) } } } fun main(args: Array<String>) { val demo = Outer().Inner().foo() println(demo) // 1 val demo2 = Outer().Inner().innerTest() println(demo2) // 内部类可以引用外部类的成员,例如:成员属性 } //1 //内部类可以引用外部类的成员,例如:成员属性 //kotlin.Unit
inner
带有一个对外部类的对象的引用,所以内部类可以访问外部类成员属性和成员函数。
匿名内部类
class Test { var v = "成员属性" fun setInterFace(test: TestInterFace) { test.test() } } /** * 定义接口 */ interface TestInterFace { fun test() } fun main(args: Array<String>) { var test = Test() /** * 采用对象表达式来创建接口对象,即匿名内部类的实例。 */ test.setInterFace(object : TestInterFace { override fun test() { println("对象表达式创建匿名内部类的实例") } }) } //对象表达式创建匿名内部类的实例
使用对象表达式来创建匿名内部类
类的修饰符
// 文件名:example.kt package foo private fun foo() {} // 在 example.kt 内可见 public var bar: Int = 5 // 该属性随处可见 internal val baz = 6 // 相同模块内可见
classModifier: 类属性修饰符,标示类本身特性
abstract // 抽象类
final // 类不可继承,默认属性
enum // 枚举类
open // 类可继承,类默认是final的
annotation // 注解类
accessModifier: 访问权限修饰符
private // 仅在同一个文件中可见
protected // 同一个文件中或子类可见
public // 所有调用的地方都可见
internal // 同一个模块中可见
继承
Kotlin 继承
open class Base(p: Int) // 定义基类 class Derived(p: Int) : Base(p)
Kotlin 中所有类都继承该 Any 类,它是所有类的超类
Any 默认提供了三个函数
equals()
hashCode()
toString()
构造函数
子类有主构造函数
open class Person(var name : String, var age : Int){// 基类 } class Student(name : String, age : Int, var no : String, var score : Int) : Person(name, age) { } // 测试 fun main(args: Array<String>) { val s = Student("Runoob", 18, "S12346", 89) println("学生名: ${s.name}") println("年龄: ${s.age}") println("学生号: ${s.no}") println("成绩: ${s.score}") } //学生名: Runoob //年龄: 18 //学生号: S12346 //成绩: 89
则基类必须在主构造函数中立即初始化
子类没有主构造函数
/**用户基类**/ open class Person(name:String){ /**次级构造函数**/ constructor(name:String,age:Int):this(name){ //初始化 println("-------基类次级构造函数---------") } } /**子类继承 Person 类**/ class Student:Person{ /**次级构造函数**/ constructor(name:String,age:Int,no:String,score:Int):super(name,age){ println("-------继承类次级构造函数---------") println("学生名: ${name}") println("年龄: ${age}") println("学生号: ${no}") println("成绩: ${score}") } } fun main(args: Array<String>) { var s = Student("Runoob", 18, "S12345", 89) } //-------基类次级构造函数--------- //-------继承类次级构造函数--------- //学生名: Runoob //年龄: 18 //学生号: S12345 //成绩: 89
必须在每一个二级构造函数中用 super 关键字初始化基类,或者在代理另一个构造函数
重写
/**用户基类**/ open class Person{ open fun study(){ // 允许子类重写 println("我毕业了") } } /**子类继承 Person 类**/ class Student : Person() { override fun study(){ // 重写方法 println("我在读大学") } } fun main(args: Array<String>) { val s = Student() s.study(); } 输出结果为: 我在读大学
在基类中,使用fun声明函数时,此函数默认为final修饰,不能被子类重写。
如果允许子类重写该函数,那么就要手动添加 open 修饰它, 子类重写方法使用 override 关键词
如果有多个相同的方法(继承或者实现自其他类,如A、B类),则必须要重写该方法,使用super范型去选择性地调用父类的实现。
open class A { open fun f () { print("A") } fun a() { print("a") } } interface B { fun f() { print("B") } //接口的成员变量默认是 open 的 fun b() { print("b") } } class C() : A() , B{ override fun f() { super<A>.f()//调用 A.f() super<B>.f()//调用 B.f() } } fun main(args: Array<String>) { val c = C() c.f(); } //AB
属性重写
使用 override 关键字,属性必须具有兼容类型
open class Foo { open val x: Int get { …… } } class Bar1 : Foo() { override val x: Int = …… }
可以用一个var属性重写一个val属性,但是反过来不行。因为val属性本身定义了getter方法
interface Foo { val count: Int } class Bar1(override val count: Int) : Foo class Bar2 : Foo { override var count: Int = 0 }
接口
interface MyInterface { fun bar() // 未实现 fun foo() { //已实现 // 可选的方法体 println("foo") } }
与 Java 8 类似,使用 interface 关键字定义接口,允许方法有默认实现
interface MyInterface { fun bar() fun foo() { // 可选的方法体 println("foo") } } class Child : MyInterface { override fun bar() { // 方法体 println("bar") } } fun main(args: Array<String>) { val c = Child() c.foo(); c.bar(); } //foo //bar
接口中的属性
interface MyInterface { var name:String //name 属性, 抽象的 fun bar() fun foo() { // 可选的方法体 println("foo") } } class Child : MyInterface { override var name: String = "runoob" //重写属性 override fun bar() { // 方法体 println("bar") } } fun main(args: Array<String>) { val c = Child() c.foo(); c.bar(); println(c.name) } 输出结果为: foo bar runoob
接口中的属性只能是抽象的,不允许初始化值,接口不会保存属性值,实现接口时,必须重写属性
函数重写
interface A { fun foo() { print("A") } // 已实现 fun bar() // 未实现,没有方法体,是抽象的 } interface B { fun foo() { print("B") } // 已实现 fun bar() { print("bar") } // 已实现 } class C : A { override fun bar() { print("bar") } // 重写 } class D : A, B { override fun foo() { super<A>.foo() super<B>.foo() } override fun bar() { super<B>.bar() } } fun main(args: Array<String>) { val d = D() d.foo(); d.bar(); } //ABbar
实现多个接口时,可能会遇到同一方法继承多个实现的问题
扩展
扩展函数
fun receiverType.functionName(params){ body } receiverType:表示函数的接收者,也就是函数扩展的对象 functionName:扩展函数的名称 params:扩展函数的参数,可以为NULL
扩展函数可以在已有类中添加新的方法,不会对原类做修改
class User(var name: String) /**扩展函数**/ fun User.Print() { print("用户名 $name") } fun main(arg: Array<String>) { var user = User("Runoob") user.Print() } //用户名 Runoob
扩展函数是静态解析的
open class C class D : C() fun C.foo() = "c" // 扩展函数 foo fun D.foo() = "d" // 扩展函数 foo fun printFoo(c: C) { println(c.foo()) // 类型是 C 类 } fun main(arg: Array<String>) { printFoo(D()) } //c
若扩展函数和成员函数一致,则使用该函数时,会优先使用成员函数。
class C { fun foo() { println("成员函数") } } fun C.foo() { println("扩展函数") } fun main(arg:Array<String>){ var c = C() c.foo() } //成员函数
扩展一个空对象
在扩展函数内, 可以通过 this 来判断接收者是否为 NULL,这样,即使接收者为 NULL,也可以调用扩展函数。
fun Any?.toString(): String { if (this == null) return "null" // 空检测之后,“this”会自动转换为非空类型,所以下面的 toString() // 解析为 Any 类的成员函数 return toString() } fun main(arg:Array<String>){ var t = null println(t.toString()) } 实例执行输出结果为: null
伴生对象的扩展
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数和属性。
class MyClass { companion object { } // 将被称为 "Companion" } fun MyClass.Companion.foo() { println("伴随对象的扩展函数") } val MyClass.Companion.no: Int get() = 10 fun main(args: Array<String>) { println("no:${MyClass.no}") MyClass.foo() } 实例执行输出结果为: no:10 伴随对象的扩展函数
扩展声明为成员
在一个类内部你可以为另一个类声明扩展
class D { fun bar() { println("D bar") } } class C { fun baz() { println("C baz") } fun D.foo() { bar() // 调用 D.bar baz() // 调用 C.baz } fun caller(d: D) { d.foo() // 调用扩展函数 } } fun main(args: Array<String>) { val c: C = C() val d: D = D() c.caller(d) } //D bar //C baz
数据类与密封类
数据类
data class User(val name: String, val age: Int)
Kotlin 可以创建一个只包含数据的类,关键字为 data:
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数
equals()
hashCode()
toString()
格式如
"User(name=John, age=42)"
componentN() functions
对应于属性,按声明顺序排列
copy()
data class User(val name: String, val age: Int) fun main(args: Array<String>) { val jack = User(name = "Jack", age = 1) val olderJack = jack.copy(age = 2) println(jack) println(olderJack) } 输出结果为: User(name=Jack, age=1) User(name=Jack, age=2)
密封类
用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。
使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中
泛型
即 "参数化类型",将类型参数化,可以用在类,接口,方法上。
class Box<T>(t: T) { var value = t }
以下实例向泛型类传入整型数据和字符串
class Box<T>(t : T) { var value = t } fun main(args: Array<String>) { var boxInt = Box<Int>(10) var boxString = Box<String>("Runoob") println(boxInt.value) println(boxString.value) } 输出结果为: 10 Runoob
在调用泛型函数时,如果可以推断出类型参数,可以省略泛型参数
fun main(args: Array<String>) { val age = 23 val name = "runoob" val bool = true doPrintln(age) // 整型 doPrintln(name) // 字符串 doPrintln(bool) // 布尔型 } fun <T> doPrintln(content: T) { when (content) { is Int -> println("整型数字为 $content") is String -> println("字符串转换为大写:${content.toUpperCase()}") else -> println("T 不是整型,也不是字符串") } } 输出结果为: 整型数字为 23 字符串转换为大写:RUNOOB T 不是整型,也不是字符串
泛型约束
可以使用泛型约束来设定一个给定参数允许使用的类型
最常见的约束是上界(upper bound)
型变
Kotlin 中没有通配符类型,它有两个其他的东西:声明处型变(declaration-site variance)与类型投影(type projections)。
声明处型变
使用 out 使得一个类型参数协变,协变类型参数只能用作输出,可以作为返回值类型但是无法作为入参的类型
// 定义一个支持协变的类 class Runoob<out A>(val a: A) { fun foo(): A { return a } } fun main(args: Array<String>) { var strCo: Runoob<String> = Runoob("a") var anyCo: Runoob<Any> = Runoob<Any>("b") anyCo = strCo println(anyCo.foo()) } //a
in 使得一个类型参数逆变,逆变类型参数只能用作输入,可以作为入参的类型但是无法作为返回值的类型:
// 定义一个支持逆变的类 class Runoob<in A>(a: A) { fun foo(a: A) { } } fun main(args: Array<String>) { var strDCo = Runoob("a") var anyDCo = Runoob<Any>("b") strDCo = anyDCo }
星号投射
对泛型类型定义一个类型投射, 要求这个泛型类型的所有的实体实例, 都是这个投射的子类型。
Function<*, String> , 代表 Function<in Nothing, String> ;
Function<Int, *> , 代表 Function<Int, out Any?> ;
Function<, > , 代表 Function<in Nothing, out Any?> .
枚举类
枚举类最基本的用法是实现一个类型安全的枚举。
枚举常量用逗号分隔,每个枚举常量都是一个对象。
枚举初始化
enum class Color(val rgb: Int) { RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF) }
对象表达式/声明
对象表达式
委托
在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托。
类委托
即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
// 创建接口 interface Base { fun print() } // 实现此接口的被委托的类 class BaseImpl(val x: Int) : Base { override fun print() { print(x) } } // 通过关键字 by 建立委托类 class Derived(b: Base) : Base by b fun main(args: Array<String>) { val b = BaseImpl(10) Derived(b).print() // 输出 10 }
属性委托
val/var <属性名>: <类型> by <表达式>
var/val:属性类型(可变/只读)
属性名:属性名称
类型:属性的数据类型
表达式:委托代理类
定义一个被委托的类
import kotlin.reflect.KProperty // 定义包含属性委托的类 class Example { var p: String by Delegate() } // 委托的类 class Delegate { operator fun getValue(thisRef: Any?, property: KProperty<*>): String { return "$thisRef, 这里委托了 ${property.name} 属性" } operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) { println("$thisRef 的 ${property.name} 属性赋值为 $value") } } fun main(args: Array<String>) { val e = Example() println(e.p) // 访问该属性,调用 getValue() 函数 e.p = "Runoob" // 调用 setValue() 函数 println(e.p) } //Example@617c74e5, 这里委托了 p 属性 //Example@617c74e5 的 p 属性赋值为 Runoob //Example@617c74e5, 这里委托了 p 属性
该类需要包含 getValue() 方法和 setValue() 方法,且参数 thisRef 为进行委托的类的对象,prop 为进行委托的属性的对象。
标准委托
Kotlin 的标准库中已经内置了很多工厂方法来实现属性的委托。
延迟属性 Lazy
val lazyValue: String by lazy { println("computed!") // 第一次调用输出,第二次调用不执行 "Hello" } fun main(args: Array<String>) { println(lazyValue) // 第一次执行,执行两次输出表达式 println(lazyValue) // 第二次执行,只输出返回值 } 执行输出结果: computed! Hello Hello
lazy() 是一个函数, 接受一个 Lambda 表达式作为参数, 返回一个 Lazy <T> 实例的函数,返回的实例可以作为实现延迟属性的委托: 第一次调用 get() 会执行已传递给 lazy() 的 lamda 表达式并记录结果, 后续调用 get() 只是返回记录的结果。
可观察属性 Observable
import kotlin.properties.Delegates class User { var name: String by Delegates.observable("初始值") { prop, old, new -> println("旧值:$old -> 新值:$new") } } fun main(args: Array<String>) { val user = User() user.name = "第一次赋值" user.name = "第二次赋值" } 执行输出结果: 旧值:初始值 -> 新值:第一次赋值 旧值:第一次赋值 -> 新值:第二次赋值
observable 可以用于实现观察者模式。
Delegates.observable() 函数接受两个参数: 第一个是初始化值, 第二个是属性值变化事件的响应器(handler)。
在属性赋值后会执行事件的响应器(handler),它有三个参数:被赋值的属性、旧值和新值:
把属性储存在映射中
class Site(val map: Map<String, Any?>) { val name: String by map val url: String by map } fun main(args: Array<String>) { // 构造函数接受一个映射参数 val site = Site(mapOf( "name" to "菜鸟教程", "url" to "www.runoob.com" )) // 读取映射值 println(site.name) println(site.url) } 执行输出结果: 菜鸟教程 www.runoob.com
一个常见的用例是在一个映射(map)里存储属性的值。 这经常出现在像解析 JSON 或者做其他"动态"事情的应用中。 在这种情况下,你可以使用映射实例自身作为委托来实现委托属性。
如果使用 var 属性,需要把 Map 换成 MutableMap:
class Site(val map: MutableMap<String, Any?>) { val name: String by map val url: String by map } fun main(args: Array<String>) { var map:MutableMap<String, Any?> = mutableMapOf( "name" to "菜鸟教程", "url" to "www.runoob.com" ) val site = Site(map) println(site.name) println(site.url) println("--------------") map.put("name", "Google") map.put("url", "www.google.com") println(site.name) println(site.url) } 执行输出结果: 菜鸟教程 www.runoob.com -------------- Google www.google.com
Not Null
class Foo { var notNullBar: String by Delegates.notNull<String>() } foo.notNullBar = "bar" println(foo.notNullBar)
notNull 适用于那些无法在初始化阶段就确定属性值的场合。
需要注意,如果属性在赋值前就被访问的话则会抛出异常。
局部委托属性
fun example(computeFoo: () -> Foo) { val memoizedFoo by lazy(computeFoo) if (someCondition && memoizedFoo.isValid()) { memoizedFoo.doSomething() } }
你可以将局部变量声明为委托属性。 例如,你可以使一个局部变量惰性初始化:
memoizedFoo 变量只会在第一次访问时计算。 如果 someCondition 失败,那么该变量根本不会计算。
属性委托要求
对于只读属性(也就是说val属性), 它的委托必须提供一个名为getValue()的函数。该函数接受以下参数:
thisRef —— 必须与属性所有者类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
property —— 必须是类型 KProperty<*> 或其超类型
这个函数必须返回与属性相同的类型(或其子类型)。
对于一个值可变(mutable)属性(也就是说,var 属性),除 getValue()函数之外,它的委托还必须 另外再提供一个名为setValue()的函数, 这个函数接受以下参数:
property —— 必须是类型 KProperty<*> 或其超类型
new value —— 必须和属性同类型或者是它的超类型。
翻译规则
在每个委托属性的实现的背后,Kotlin 编译器都会生成辅助属性并委托给它。
例如,对于属性 prop,生成隐藏属性 prop$delegate,而访问器的代码只是简单地委托给这个附加属性: class C { var prop: Type by MyDelegate() } // 这段是由编译器生成的相应代码: class C { private val prop$delegate = MyDelegate() var prop: Type get() = prop$delegate.getValue(this, this::prop) set(value: Type) = prop$delegate.setValue(this, this::prop, value) }
提供委托
class ResourceLoader<T>(id: ResourceID<T>) { operator fun provideDelegate( thisRef: MyUI, prop: KProperty<*> ): ReadOnlyProperty<MyUI, T> { checkProperty(thisRef, prop.name) // 创建委托 } private fun checkProperty(thisRef: MyUI, name: String) { …… } } fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { …… } class MyUI { val image by bindResource(ResourceID.image_id) val text by bindResource(ResourceID.text_id) }
通过定义 provideDelegate 操作符,可以扩展创建属性实现所委托对象的逻辑。 如果 by 右侧所使用的对象将 provideDelegate 定义为成员或扩展函数,那么会调用该函数来 创建属性委托实例。
thisRef —— 必须与 属性所有者 类型(对于扩展属性——指被扩展的类型)相同或者是它的超类型
property —— 必须是类型 KProperty<*> 或其超类型。