本文已收录至学习笔记大全:JavaKotlinAndroidGuidejava
做者:leavesCgit
[TOC]github
扩展函数用于为一个类增长一种新的行为,这是为缺乏有用函数的类进行扩展的途径。扩展函数的用途就相似于在 Java 中实现的静态工具方法。而在 kotlin 中使用扩展函数的一个优点就是咱们不须要在调用方法的时候把整个对象看成参数传入,扩展函数表现得就像是属于这个类自己的同样,可使用 this 关键字并直接调用其全部 public 方法设计模式
扩展函数并不容许你打破它的封装性,和在类内部定义的方法不一样的是,扩展函数不能访问私有的或是受保护的成员数组
//为 String 类声明一个扩展函数 lastChar() ,用于返回字符串的最后一个字符
//get方法是 String 类的内部方法,length 是 String 类的内部成员变量,在此处能够直接调用
fun String.lastChar() = get(length - 1)
//为 Int 类声明一个扩展函数 doubleValue() ,用于返回其两倍值
//this 关键字表明了 Int 值自己
fun Int.doubleValue() = this * 2
复制代码
以后,咱们就能够像调用类自己内部声明的方法同样,直接调用扩展函数安全
fun main() {
val name = "leavesC"
println("$name lastChar is: " + name.lastChar())
val age = 24
println("$age doubleValue is: " + age.doubleValue())
}
复制代码
若是须要声明一个静态的扩展函数,则必须将其定义在伴生对象上,这样就能够在没有 Namer 实例的状况下调用其扩展函数,就如同在调用 Java 的静态函数同样bash
class Namer {
companion object {
val defaultName = "mike"
}
}
fun Namer.Companion.getName(): String {
return defaultName
}
fun main() {
Namer.getName()
}
复制代码
须要注意的是,若是扩展函数声明于 class 内部,则该扩展函数只能该类和其子类内部调用,由于此时至关于声明了一个非静态函数,外部没法引用到。因此通常都是将扩展函数声明为全局函数闭包
扩展函数也能够用于属性app
//扩展函数也能够用于属性
//为 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
}
复制代码
看如下例子,子类 Button 重写了父类 View 的 click() 函数,此时若是声明一个 View 变量,并赋值为 Button 类型的对象,调用的 click() 函数将是 Button 类重写的方法less
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")
}
复制代码
对于扩展函数来讲,与以上的例子却不同。若是基类和子类都分别定义了一个同名的扩展函数,此时要调用哪一个扩展函数是由变量的静态类型来决定的,而非这个变量的运行时类型
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")
复制代码
此外,若是一个类的成员函数和扩展函数有相同的签名,成员函数会被优先使用
扩展函数并非真正地修改了原来的类,其底层实际上是以静态导入的方式来实现的。扩展函数能够被声明在任何一个文件中,所以有个通用的实践是把一系列有关的函数放在一个新建的文件里
须要注意的是,扩展函数不会自动地在整个项目范围内生效,若是须要使用到扩展函数,须要进行导入
能够为可空的接收者类型定义扩展,即便接受者为 null,使得开发者在调用扩展函数前没必要进行判空操做,且能够经过 this == null
来检查接收者是否为空
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")
}
复制代码
Lambda 表达式本质上就是能够传递给其它函数的一小段代码,经过 Lambda 表达式能够把通用的代码结构抽取成库函数,也能够把 Lambda 表达式存储在一个变量中,把这个变量当作普通函数对待
//因为存在类型推导,因此如下三种声明方式都是彻底相同的
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))
复制代码
虽说倾向于尽可能避免让 Lambda 表达式引用外部变量以免反作用,但有些状况下让 Lambda 引用外部变量也能够简化计算结构。访问了外部环境变量的 Lambda 表达式称之为闭包,闭包能够被当作参数传递或者直接使用。与 Java 不一样,kotlin 中的闭包不只能够访问外部变量也能够对其进行修改
例如,假设咱们须要一个计算总和的方法,每次调用函数时都返回当前的总和大小。方法外部不提供保存当前总和的变量,由 Lambda 表达式内部进行存储
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
}
}
复制代码
此外,kotlin 也支持一种自动运行的语法
{ va1: Int, va2: Int -> println(va1 + va2) }(10, 20)
复制代码
Lambda 表达式最多见的用途就是和集合一块儿工做,看如下例子
要从一我的员列表中取出年龄最大的一位
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)
}
复制代码
当中,库函数 maxBy 能够在任何集合上调用,其须要一个实参:一个函数,用于指定要用来进行比较的函数。花括号中的代码 { it.age }
就是实现了这个逻辑的 Lambda 表达式
上述 maxBy 函数的实参是简化后的写法,这里来看下 maxBy 函数的简化过程
最原始的语法声明应该是这样的,用括号包裹着 Lambda 表达式
println(people.maxBy({ p: Person -> p.age }))
复制代码
kotlin 有一种语法约定,若是 Lambda 表达式是函数调用的最后一个实参,能够将之放到括号的外边
println(people.maxBy() { p: Person -> p.age })
复制代码
当 Lamdba 表达式是函数惟一的实参时,能够去掉调用代码中的空括号对
println(people.maxBy { p: Person -> p.age })
复制代码
当 Lambda 表达式的参数类型是能够被推导出来时就能够省略声明参数类型
println(people.maxBy { p -> p.age })
复制代码
若是当前上下文期待的是只有一个参数的 Lambda 表达式且参数类型能够被推断出来,就会为该参数生成一个默认名称:it
println(people.maxBy { it.age })
复制代码
kotlin 和 Java 的一个显著区别就是,在 kotlin 中函数内部的 Lambda 表达式不会仅限于访问函数的参数以及 final 变量,在 Lambda 内部也能够访问并修改非 final 变量
从 Lambda 内部访问外部变量,咱们称这些变量被 Lambda 捕捉。当捕捉 final 变量时,变量值和使用这个值的 Lambda 代码一块儿存储,对非 final 变量来讲,其值被封装在一个特殊的包装器中,对这个包装器的引用会和 Lambda 代码一块儿存储
var number = 0
val list = listOf(10, 20, 30, 40)
list.forEach {
if (it > 20) {
number++
}
}
println(number) //2
复制代码
成员引用用于建立一个调用单个方法或者访问单个属性的函数值,经过双冒号把类名称和要引用的成员(一个方法或者一个属性)名称分隔开
成员引用的一个用途就是:若是要当作参数传递的代码块已经被定义成了函数,此时没必要专门建立一个调用该函数的 Lambda 表达式,能够直接经过成员引用的方式来传递该函数(也能够传递属性)。此外,成员引用对扩展函数同样适用
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)
}
复制代码
无论引用的是函数仍是属性,都不要在成员引用的名称后面加括号
此外,还能够引用顶层函数
fun test() {
println("test")
}
fun main() {
val t = ::test
}
复制代码
也能够用构造方法引用存储或者延期执行建立类实例的动做
data class Person(val name: String, val age: Int)
fun main() {
val createPerson = ::Person
val person = createPerson("leavesC", 24)
println(person)
}
复制代码
kotlin 标准库中提供了几个比较实用的扩展函数,定义在 Standard 文件下
run 函数接收一个函数参数并以该函数的返回值做为 run 函数的返回值
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
复制代码
用例
fun main() {
var nickName = "leavesC"
nickName = nickName.run {
if (isNotEmpty()) {
this
} else {
""
}
}
println(nickName)
}
复制代码
with 函数并非扩展函数,不过因为做用相近,此处就一块儿介绍了。with 函数的第一个参数是接受者对象 receiver,第二个参数是在 receiver 对象类型上定义的扩展函数,因此能够在函数内部直接调用 receiver 其公开的方法和属性
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
复制代码
with 函数用于对同一个对象执行屡次操做而不须要反复把对象的名称写出来
例如,为了构建一个包含指定内容的字符串,须要前后以下调用
fun main() {
val result = StringBuilder()
result.append("leavesC")
result.append("\n")
for (letter in 'A'..'Z') {
result.append(letter)
}
println(result.toString())
}
复制代码
改成经过 with 函数来构建的话会代码会简洁许多
val result = with(StringBuilder()) {
append("leavesC")
append("\n")
for (letter in 'A'..'Z') {
append(letter)
}
toString()
}
println(result)
复制代码
with 函数是一个接受两个参数的函数,在这个例子中就是一个 StringBuilder 和一个 Lambda 表达式,这里利用了把 Lambda 表达式放在括号外的约定
with 函数的返回值是执行 Lambda 表达式的结果,该结果就是 Lambda 中的最后一个表达式的返回值,所以若是将代码修改成以下所示的话,由于 println() 方法无返回值,因此打印出来的内容将是 kotlin.Unit
val result = with(StringBuilder()) {
append("leavesC")
append("\n")
for (letter in 'A'..'Z') {
append(letter)
}
println("Hello")
}
println(result) //kotin.Unit
复制代码
apply 函数被声明为类型 T 的扩展函数,它的接收者是做为实参的 Lambda 的接受者,最终函数返回 this 即对象自己
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
复制代码
因此apply 函数和 with 函数的惟一区别在于:apply 函数始终会返回做为实参传递给它的对象
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
复制代码
also 函数接收一个函数类型的参数,该参数又以接收者自己做为参数,最终返回接收者对象自己
@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
}
复制代码
用例
fun main() {
val nickName = "leavesC"
val also = nickName.also {
it.length
}
println(also) //leavesC
}
复制代码
also 函数接收一个函数类型的参数,该参数又以接收者自己做为参数,最终返回函数的求值结果
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
复制代码
用例
fun main() {
val nickName = "leavesC"
val also = nickName.let {
it.length
}
println(also) //7
}
复制代码
takeIf 接收一个返回值类型为 bool 的函数,当该参数返回值为 true 时返回接受者对象自己,不然返回 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
}
复制代码
用例
fun main() {
println(check("leavesC")) //7
println(check(null)) //0
}
fun check(name: String?): Int {
return name.takeIf { !it.isNullOrBlank() }?.length ?: 0
}
复制代码
takeUnless 的判断条件与 takeIf 相反,这里再也不赘述
@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
}
复制代码
若是至少有一个元素符合给出的判断条件,则返回 true
val list = listOf(1, 3, 5, 7, 9)
println(list.any { it > 13 }) //false
println(list.any { it > 7 }) //true
复制代码
若是所有的元素符合给出的判断条件,则返回 true
val list = listOf(1, 3, 5, 7, 9)
println(list.all { it > 13 }) //false
println(list.all { it > 0 }) //true
复制代码
返回符合给出判断条件的元素总数
val list = listOf(1, 3, 5, 7, 9)
println(list.count { it > 7 }) //1
println(list.count { it > 2 }) //4
复制代码
在一个初始值的基础上从第一项到最后一项经过一个函数累计全部的元素
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
复制代码
与 fold 同样,但顺序是从最后一项到第一项
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
复制代码
val list = listOf(1, 3, 5, 7, 9)
list.forEach { print(it + 1) } //246810
复制代码
相似于 forEach ,同时能够获得元素的索引
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
复制代码
返回最大的一项,若是没有则返回null
val list = listOf(1, 3, 5, 7, 9)
println(list.max()) //9
复制代码
根据给定的函数返回最大的一项,若是没有则返回 null
val list = listOf(1, 3, 5, 7, 9)
println(list.maxBy { -it }) //1
复制代码
返回最小的一项,若是没有则返回null
val list = listOf(1, 3, 5, 7, 9)
println(list.min()) //1
复制代码
根据给定的函数返回最小的一项,若是没有则返回null
val list = listOf(1, 3, 5, 7, 9)
println(list.minBy { -it }) //9
复制代码
若是没有任何元素与给定的函数匹配,则返回true
val list = listOf(1, 3, 5, 7, 9)
println(list.none { it > 10 }) //true
复制代码
与 fold 同样,可是没有一个初始值。经过一个函数从第一项到最后一项进行累计
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
复制代码
与 reduce 同样,可是顺序是从最后一项到第一项
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
复制代码
返回全部每一项经过函数转换以后的数据的总和
val list = listOf(1, 3, 5, 7, 9)
println(list.sumBy { it + 1 }) //30
复制代码
返回包含去掉前n个元素的全部元素的列表
val list = listOf(1, 3, 5, 7, 9)
println(list.drop(2)) //[5, 7, 9]
复制代码
返回从第一个开始不符合给定函数的元素起以后的列表
val list = listOf(1, 3, 5, 7, 9, 2)
println(list.dropWhile { it < 4 }) //[5, 7, 9, 2]
复制代码
从最后一项开始,返回从开始不符合给定函数的元素起以后的列表
val list = listOf(10, 1, 3, 5, 7, 9)
println(list.dropLastWhile { it > 4 }) //[10, 1, 3]
复制代码
过滤全部符合给定函数条件的元素
val list = listOf(1, 3, 5, 7, 9, 2)
println(list.filter { it < 4 }) //[1, 3, 2]
复制代码
过滤全部不符合给定函数条件的元素
val list = listOf(1, 3, 5, 7, 9, 2)
println(list.filterNot { it < 4 }) //[5, 7, 9]
复制代码
过滤全部元素中不是null的元素
val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.filterNotNull()) //[1, 3, 5, 7, 9, 2]
复制代码
过滤一个list中指定index的元素
val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.slice(listOf(0, 3))) //[1, 7]
复制代码
返回从第一个开始的n个元素
val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.take(2)) //[1, 3]
复制代码
返回从最后一个开始的n个元素
val list = listOf(1, 3, 5, 7, 9, 2, null)
println(list.takeLast(2)) //[2, null]
复制代码
返回从第一个开始符合给定函数条件的元素。
val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.takeWhile { it > 2 }) //[]
println(list.takeWhile { it > 0 }) //[1, 3, 5]
复制代码
遍历全部的元素,为每个建立一个集合,最后把全部的集合放在一个集合中
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]
复制代码
返回一个根据给定函数分组后的map
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]}
复制代码
返回一个每个元素根据给定的函数转换所组成的List。
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]]
复制代码
返回一个每个元素根据给定的包含元素index的函数转换所组成的List
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]
复制代码
返回一个每个非null元素根据给定的函数转换所组成的List
val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.mapNotNull { it }) //[1, 3, 5, -1, 7, 9, 2]
复制代码
若是指定元素能够在集合中找到,则返回true
val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.contains(3)) //true
println(list.contains(13)) //false
复制代码
返回给定index对应的元素,若是index数组越界则会抛出 IndexOutOfBoundsException
val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.elementAt(3)) //-1
println(list.elementAt(6)) //null
复制代码
返回给定index对应的元素,若是index数组越界则会根据给定函数返回默认值
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
复制代码
返回给定index对应的元素,若是index数组越界则会返回null
val list = listOf(1, 3, 5, -1, 7, 9, null, 2)
println(list.elementAtOrNull(3)) //-1
println(list.elementAtOrNull(16)) //null
复制代码
返回符合给定函数条件的第一个元素
val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.first { it % 3 == 0 }) //3
复制代码
返回符合给定函数条件的第一个元素,若是没有符合则返回null
val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.firstOrNull { it % 3 == 0 }) //3
println(list.firstOrNull { it % 8 == 0 }) //null
复制代码
返回指定元素的第一个index,若是不存在,则返回 -1
val list = listOf(1, 3, 5, -1, 7, 9, 2)
println(list.indexOf(5)) //2
println(list.indexOf(12)) //-1
复制代码
返回第一个符合给定函数条件的元素的index,若是没有符合则返回 -1
val list = listOf(1, 3, 5, 1, 7, 9, 2)
println(list.indexOfFirst { it % 2 == 0 }) //6
println(list.indexOfFirst { it % 12 == 0 }) //-1
复制代码
返回最后一个符合给定函数条件的元素的index,若是没有符合则返回 -1
val list = listOf(1, 3, 5, 6, 7, 9, 2)
println(list.indexOfLast { it % 2 == 0 }) //6
println(list.indexOfLast { it % 12 == 0 }) //-1
复制代码
返回符合给定函数条件的最后一个元素
val list = listOf(1, 3, 5, 6, 7, 9, 2)
println(list.last { it % 2 == 0 }) //2
println(list.last { it % 3 == 0 }) //9
复制代码
返回指定元素的最后一个index,若是不存在,则返回 -1
val list = listOf(1, 3, 2, 6, 7, 9, 2)
println(list.lastIndexOf(2)) //6
println(list.lastIndexOf(12)) //-1
复制代码
返回符合给定函数条件的最后一个元素,若是没有符合则返回null
val list = listOf(1, 3, 2, 6, 7, 9, 2)
println(list.lastOrNull { it / 3 == 3 }) //9
println(list.lastOrNull { it == 10 }) //null
复制代码
返回符合给定函数的单个元素,若是没有符合或者超过一个,则抛出异常
val list = listOf(1, 9, 2, 6, 7, 9, 2)
println(list.single { it % 7 == 0 }) //7
println(list.single { it == 2 }) //IllegalArgumentException
复制代码
返回符合给定函数的单个元素,若是没有符合或者超过一个,则返回null
val list = listOf(1, 9, 2, 6, 7, 9, 2)
println(list.singleOrNull { it % 7 == 0 }) //7
println(list.singleOrNull { it == 2 }) //null
复制代码
把一个给定的集合分割成两个,第一个集合是由原集合每一项元素匹配给定函数条 件返回 true 的元素组成,第二个集合是由原集合每一项元素匹配给定函数条件返回 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]
复制代码
返回一个包含原集合和给定集合中全部元素的集合,由于函数的名字缘由,咱们可使用 + 操做符
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]
复制代码
返回由 pair 组成的List,每一个 pair 由两个集合中相同index的元素组成。这个返回的List的大小由最小的那个集合决定
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)
复制代码
从包含pair的List中生成包含List的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]
复制代码
返回一个与指定list相反顺序的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)]
复制代码
返回一个天然排序后的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]
复制代码
返回一个根据指定函数排序后的list
val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sortedBy { it - 3 }
println(list2) //[1, 2, 4, 5, 9, 10]
复制代码
返回一个降序排序后的List
val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sortedDescending()
println(list2) //[10, 9, 5, 4, 2, 1]
复制代码
返回一个根据指定函数降序排序后的list
val list1 = listOf(2, 4, 1, 9, 5, 10)
val list2 = list1.sortedByDescending { it % 2 }
println(list2) //[1, 9, 5, 2, 4, 10]
复制代码
kotlin 中异常处理的基本形式和 Java 相似
fun compute(index: Int): Boolean {
if (index !in 0..10) {
throw IllegalArgumentException("参数错误")
}
return true
}
复制代码
和 Java 不一样的是,kotlin 中 throw 结构是一个表达式,能够做为另外一个表达式的一部分来使用
例以下面这个例子,若是条件不知足,则将抛出异常,从而致使 status 变量也不会初始化
val status = if (index in 0..10) index else throw IllegalArgumentException("参数错误")
复制代码
此外,在 Java 中对于受检异常必须显式地处理,经过 try/catch 语句捕获异常或者是抛给其调用者来处理。而 kotlin 不区分受检异常和未受检异常,不用指定函数抛出的异常,能够处理也能够不处理异常
在 kotlin 中 ,try 关键字引入了一个表达式,从而能够把表达式的值赋给一个变量。若是一个 try 代码块执行正常,代码块中最后一个表达式就是结果,若是捕获到了一个异常,则相应 catch 代码块中最后一个表达式就是结果
看如下例子,若是 try 表达式包裹的表达式会抛出异常,则返回值为 null ,不然为 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)
}
复制代码
可是,若是在 catch 语句中使用 return 结束了 compute 函数,则没有任何输出
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)
}
复制代码
kotlin 容许为类型提供预约义的操做符实现,这些操做符具备固定的符号表示(例如 + 和 * )和固定的优先级,经过操做符重载能够将操做符的行为映射到指定的方法。为实现这样的操做符,须要为类提供一个固定名字的成员函数或扩展函数,相应的重载操做符的函数须要用 operator 修饰符标记
操做符 | 函数 |
---|---|
+a | a.unaryPlus() |
-a | a.unaryMinus() |
!a | a.not() |
a++ | a.inc() |
a-- | a.dec() |
操做符 | 函数 |
---|---|
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) |
操做符 | 函数 |
---|---|
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) |
操做符 | 函数 |
---|---|
a == b | a?.equals(b) ?: b === null |
a != b | !(a?.equals(b) ?: b === null) |
相等操做符有一点不一样,为了达到正确合适的相等检查作了更复杂的转换,由于要获得一个确切的函数结构比较,不只仅是指定的名称
方法必需要以下准确地被实现:
operator fun equals(other: Any?): Boolean
复制代码
操做符 === 和 !== 用来作身份检查(它们分别是 Java 中的 == 和 != ),而且它们不能被重载
操做符 | 函数 |
---|---|
a > b | a.compareTo(b) > 0 |
a < b | a.compareTo(b) < 0 |
a >= b | a.compareTo(b) >= 0 |
a <= b | a.compareTo(b) <= 0 |
全部的比较都转换为对 compareTo 的调用,这个函数须要返回 Int 值
方法 | 调用 |
---|---|
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) |
看几个例子
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)}")
}
复制代码
能够以如下形式建立一个 Map 变量
fun main() {
val maps = mapOf(1 to "leavesC", 2 to "ye", 3 to "czy")
maps.forEach { key, value -> println("key is : $key , value is : $value") }
}
复制代码
使用 “to” 来声明 map 的 key 与 value 之间的对应关系,这种形式的函数调用被称为中缀调用
kotlin 标准库中对 to 函数的声明以下所示,其做为扩展函数存在,且是一个泛型函数,返回值 Pair 最终再经过解构声明分别将 key 和 value 传给 Map
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")
复制代码
有时会有把一个对象拆解成多个变量的需求,在 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")
}
复制代码
在 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();
}
复制代码
若是须要一个能够在没有类实例的状况下调用可是须要访问类内部的函数(相似于 Java 中的静态变量/静态函数),能够将其写成那个类中的对象声明的成员
经过关键字 companion ,就能够得到经过容器类名称来访问这个对象的方法和属性的能力,再也不须要显式地指明对象的名称
class Test {
companion object {
const val NAME = ""
fun testFun() {
}
}
}
fun main() {
Test.NAME
Test.testFun()
}
复制代码
能够利用伴生对象来实现工厂模式
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)
}
复制代码
伴生对象既能够为其指定名字,也能够直接使用其默认名 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)
}
复制代码
伴生对象也能够实现接口,且能够直接将包含它的类的名字当作实现了该接口的对象实例来使用
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)
}
复制代码
object 能用来声明匿名对象,可用于替代 Java 中的匿名内部类,且对象表达式中的代码能够访问并修改其外部的非 final 型的变量
fun newThread(runnable: Runnable) = Thread(runnable)
fun main() {
var count = 0
val thread = newThread(object : Runnable {
override fun run() {
count++
}
})
}
复制代码
委托模式是一种基本的设计模式,该模式下有两个对象参与处理同一个请求,接受请求的对象将请求委托给另外一个对象来处理。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
}
复制代码
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
}
复制代码
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 模式, 此时不会有任何线程安全的保证以及相关的资源开销
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 则表示拦截该赋值操做
}
}
复制代码
能够在一个 map 映射里存储属性的值,而后把属性的存取操做委托给 map 进行管理
fun main() {
val student = Student(
mapOf(
"name" to "leavesC",
"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
能够将局部变量声明为委托属性
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(AnnotationTarget.FUNCTION, AnnotationTarget.FIELD)
@Retention(AnnotationRetention.RUNTIME)
@Repeatable
@MustBeDocumented
annotation class AnnotationName()
复制代码
注解能够声明包含有参数的构造函数
annotation class OnClick(val viewId: Long)
复制代码
容许的参数类型有:
注解参数不能包含有可空类型,由于 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
}
}
}
}
复制代码