扩展
Kotlin 可以对一个类的属性和方法进行扩展,扩展不会对原有的类有影响
扩展函数
- 与类内部定义方法不同,扩展函数不能访问私有的或是受保护的成员
- 扩展类无法被重写
- 如果需要声明一个静态的扩展函数,则必须将其定义在伴生对象上,这样就可以在没有 Namer 实例的情况下调用其扩展函数,就如同在调用 Java 的静态函数一样
- 如果扩展函数声明于 class 内部,则该扩展函数只能该类和其子类内部调用,因为此时相当于声明了一个非静态函数,外部无法引用到。所以一般都是将扩展函数声明为全局函数
- 如果扩展函数与成员函数重名,优先使用成员函数
fun main() {
"AAA".print()
}
fun String.print(){
print(this)
}
class A{
companion object{
}
}
fun A.Companion.getName(){
}
扩展属性
- 扩展属性没有幕后字段,需要手动实现get,set方法
- 与函数一致,扩展属性不能访问私有的或是受保护的成员
- 如果要保存扩展属性的值,需要借助到类中非扩展属性
var Test.intTest : Int
get() = 1
set(value){
print(value)
}
class Test{
}
空类型安全
kotlin对象分为可空类型和不可空类型
val intTest : Int = 1
val intTest2 : Int? = null
安全操作调用符
对一个可能为空的对象使用安全操作调用符,当对象为空时,返回null,不会抛出空指针异常,当对象不为空,执行调用方法
intTest2?.and(1)
非空断言
在判断可空对象一定不为空的情况下,可使用非空断言,但是在对象为null的情况时候,非空断言会抛出空指针异常
intTest2!!.and(1)
高阶函数
高阶函数定义:高阶函数是将函数用作参数或返回值的函数。
forEach就是一个典型的高阶函数
public inline fun <T> Iterable<T>.forEach(action: (T) -> Unit): Unit {
for (element in this) action(element)
}
内联函数
用inline修饰的函数就是内联函数,Kotlin编译器会将内联函数中的代码在编译时自动替换到调用它的地方,这样就不存在运行时的开销了。一般会把高阶函数声明为内联函数,即在定义高阶函数时加上inline关键字声明,这是一种良好的编程习惯,绝大多数高阶函数是可以直接声明成内联函数的。
inline fun test() {
}
内联函数的return
fun main() {
val ints: IntArray = intArrayOf(1, 2, 3, 4)
ints.forEach {index ->
if (index == 3) {
// 跳出这一次内联函数调用;相当于 continue
return@forEach
}
println("main $index")
}
}
non-local return
这个很好理解,因为内联函数就是直接将这段代码放置到调用函数的地方,那么这个return也就等于直接在主函数中进行return了
inline fun nonLocalReturn(act:()->Unit){
act.invoke()
}
fun main() {
nonLocalReturn {
// 从main函数返回
return
}
}
crossinline 禁止外部返回
fun main() {
nonLocalReturn {
// 使用crossinline后,禁止从main函数返回
// return
}
}
内联函数的限制
- public 的内联方法只能访问对应类的 public 成员
- 内联函数的内联函数参数不能被存储(赋值给变量)
- 内联函数的内联函数参数只能传递给其他内联函数
inline/noinline/crossinline 总结
// 函数也是对象,在高阶函数中,函数参数被调用时需要被创建
// 编译后的class文件中,高阶函数中的函数参数是作为对象被创建来调用的
// 使用 inline 有优化编译效果,函数体被直接内联到class文件中,不需要再创建对象
inline fun action(run: () -> Unit) {
println("Hello")
run()
println("World")
}
// 使用 inline 后函数不能被当成对象使用,函数体被直接内联到class文件中
// 但是如果函数需要被当成对象使用,比如被当成返回值,此时就需要处理这个问题
// 使用 noinline 可以不被内联,保持函数可以被当成对象使用
inline fun action2(run: () -> Unit, noinline run2: () -> Unit): () -> Unit {
run()
return run2
}
// 因为 inline 函数中函数体被直接编译到 class 文件
// 在 inline 函数中的 return 会直接返回调用 inline 函数的上层函数
// 比如在 action 函数中使用 return ,会直接返回调用 action 函数的 main 函数
// 但如果 inline 函数中的函数类型参数,又被间接调用,则 inline 函数中的 return 无法直接返回调用 inline 函数的上层函数
// 使用 crossinline 关键字,允许函数类型参数被间接调用,但代价是不允许直接调用 return
fun func(run: () -> Unit) {
run()
}
inline fun action3(crossinline run: () -> Unit) {
func {
run()
}
}
fun main() {
// 从 action 函数返回
action {
println("Kotlin")
return@action
}
// 从 main 函数返回
action {
println("Kotlin")
return
}
// 被 crossinline 修饰的参数,允许在 inline 函数中被间接调用
// 但是不能在 Lambda 中直接使用 return;只能 return@action3
action3 {
println("Kotlin")
return@action3
}
// 使用 inline 后,创建 Lambda 表达式的开销,和调用 action 的开销都没有
action {
println("Kotlin")
}
// 使用内联后,上面action方法相当于以下代码
println("Hello")
println("Kotlin")
println("World")
}
常用高阶函数
let
// 调用者本身作为参数传入,返回值为 Lambda 表达式返回值
val age: Int = student.let {
println(it.name)
it.age
}
run
- let将上下文对象引用为it ,而run引用为this;
- run无法将“this”重命名为一个可读的lambda参数,而let可以将“it”重命名为一个可读的lambda参数。 在let多重嵌套时,就可以看到这个特点的优势所在。
// 调用者作为Receive传入,返回值为 Lambda 表达式返回值
val age2: Int = student.run {
println(name)
this.age
}
also
also与let的区别在于,also返回本身,而let返回Lambda 表达式返回值
// 调用者本身作为参数传入,返回值为自身类型
val student1: Student = student.also {
it.name = "Java"
it.age = 18
}
apply
apply与run的区别在于,apply返回本身,而run返回Lambda 表达式返回值
// 调用者作为Receive传入,返回值为自身类型
val student2: Student = student.apply {
this.name = "Java"
this.age = 18
}
with
with与run的区别在于,with是一个普通函数,而run是一个扩展函数
with(student){
println(name)
this.age
}
在with传入一个可为空的对象时,需要在使用时做安全操作
with(student){
this?.age = 1
this?.age
}
但是run就不需要,如果对象为空时,直接返回一个null
// 调用者作为Receive传入,返回值为 Lambda 表达式返回值
val age2: Int? = student?.run {
println(name)
this.age
}
use
// 在 use 方法作用域之外,相关的IO流和异常都会被释放和处理
File("file-path").inputStream().reader().buffered().use {
println(it.readLine())
}
集合操作 (参考:kotlin list集合操作 - 掘金 (juejin.cn))
总数操作符
val list = listOf(1, 2, 3, 4, 5, 6)
any
如果至少有一个元素符合给出的判断条件,则返回true。
list.any { it % 2 == 0 }
list.any { it > 10 }
all
如果全部的元素符合给出的判断条件,则返回true。
list.all { it < 10 }
list.all { it % 2 == 0 }
count
返回符合给出判断条件的元素总数。
list.count { it % 2 == 0 }
fold
在一个初始值的基础上从第一项到最后一项通过一个函数累计所有的元素。
list.fold(4) { total, next -> total + next }
foldRight
与 fold 一样,但是顺序是从最后一项到第一项。
list.foldRight(4) { total, next -> total + next}
forEach
遍历所有元素,并执行给定的操作。
list.forEach { println(it) }
forEachIndexed
与 forEach ,但是我们同时可以得到元素的index。
list.forEachIndexed { index, value-> println("position $index contains a $value") }
max
返回最大的一项,如果没有则返回null。
list.max()
maxBy
根据给定的函数返回最大的一项,如果没有则返回null。
list.maxBy { -it }
min
返回最小的一项,如果没有则返回null。
assertEquals(1, list.min())
minBy
根据给定的函数返回最小的一项,如果没有则返回null。
// 负数较小的元素
assertEquals(6, list.minBy { -it })
none
如果没有任何元素与给定的函数匹配,则返回true。
//没有元素可以被7整除
list.none { it % 7 == 0 }
reduce
与 fold 一样,但是没有一个初始值。通过一个函数从第一项到最后一项进行累 计。但是与fold不同的是,如果列表为空,reduce会抛出异常
list.reduce { total, next -> total + next }
reduceRight
与 reduce 一样,但是顺序是从最后一项到第一项。
list.reduceRight { total, next -> total + next}
sumBy
返回所有每一项通过函数转换之后的数据的总和。
list.sumBy { it % 2 }
过滤操作符
drop
返回包含去掉前n个元素的所有元素的列表。
list.drop(4)
dropWhile
返回根据给定函数从第一项开始去掉指定元素的列表。
list.dropWhile { it < 3 }
dropLastWhile
返回根据给定函数从最后一项开始去掉指定元素的列表。
list.dropLastWhile { it > 4 }
filter
过滤所有符合给定函数条件的元素。
list.filter { it % 2 == 0 }
filterNot
过滤所有不符合给定函数条件的元素。
list.filterNot { it % 2 == 0 }
filterNotNull
过滤所有元素中不是null的元素。
listWithNull.filterNotNull()
slice
过滤一个list中指定index的元素。
list.slice(listOf(1, 3, 4))
take
返回从第一个开始的n个元素。
list.take(2)
takeLast
返回从最后一个开始的n个元素
list.takeLast(2)
takeWhile
返回从第一个开始符合给定函数条件的元素。
list.takeWhile { it < 3 }
映射操作符
flatMap
遍历所有的元素,为每一个创建一个集合,最后把所有的集合放在一个集合中。 与map相似,返回值不同
list.flatMap { listOf(it, it + 1)}
groupBy
返回一个根据给定函数分组后的map。
list.groupBy { if (it % 2 == 0) "even" else "odd" }
map
返回一个每一个元素根据给定的函数转换所组成的List。
list.map { it * 2 }
mapIndexed
返回一个每一个元素根据给定的包含元素index的函数转换所组成的List。
list.mapIndexed { index, it -> index * it }
mapNotNull
返回一个每一个非null元素根据给定的函数转换所组成的List。
listWithNull.mapNotNull { it * 2}
元素操作符
contains
如果指定元素可以在集合中找到,则返回true。
list.contains(2)
elementAt
返回给定index对应的元素,如果index数组越界则会抛 出 IndexOutOfBoundsException 。
list.elementAt(1)
elementAtOrElse
返回给定index对应的元素,如果index数组越界则会根据给定函数返回默认值。
list.elementAtOrElse(10, { 2 * it })
elementAtOrNull
返回给定index对应的元素,如果index数组越界则会返回null。
list.elementAtOrNull(10)
first
返回符合给定函数条件的第一个元素。
list.first { it % 2 == 0 }
firstOrNull
返回符合给定函数条件的第一个元素,如果没有符合则返回null。
list.firstOrNull { it % 7 == 0 }
indexOf
返回指定元素的第一个index,如果不存在,则返回 -1 。
list.indexOf(4)
indexOfFirst
返回第一个符合给定函数条件的元素的index,如果没有符合则返回 -1 。
list.indexOfFirst { it % 2 == 0 }
indexOfLast
返回最后一个符合给定函数条件的元素的index,如果没有符合则返回 -1 。
list.indexOfLast { it % 2 == 0 }
last
返回符合给定函数条件的最后一个元素。
list.last { it % 2 == 0 }
lastIndexOf
返回指定元素的最后一个index,如果不存在,则返回 -1 。
lastOrNull
返回符合给定函数条件的最后一个元素,如果没有符合则返回null。
list.lastOrNull { it % 7 == 0 }
single
返回符合给定函数的单个元素,如果没有符合或者超过一个,则抛出异常。
list.single { it % 5 == 0 }
singleOrNull
返回符合给定函数的单个元素,如果没有符合或者超过一个,则返回null。
list.singleOrNull { it % 7 == 0 }
生产操作符
merge
把两个集合合并成一个新的,相同index的元素通过给定的函数进行合并成新的元素 作为新的集合的一个元素,返回这个新的集合。新的集合的大小由最小的那个集合 大小决定。
val list = listOf(1, 2, 3, 4, 5, 6)
val listRepeated = listOf(2, 2, 3, 4, 5, 5, 6)
list.merge(listRepeated) { it1, it2 -> it1 + it2 }
partition
把一个给定的集合分割成两个,第一个集合是由原集合每一项元素匹配给定函数条 件返回 true 的元素组成,第二个集合是由原集合每一项元素匹配给定函数条件返 回 false 的元素组成。
list.partition { it % 2 == 0 }
plus
返回一个包含原集合和给定集合中所有元素的集合,因为函数的名字原因,我们可 以使用 + 操作符。
list + listOf(7, 8))
zip
返回由 pair 组成的List,每个 pair 由两个集合中相同index的元素组成。这个返 回的List的大小由最小的那个集合决定。
list.zip(listOf(7, 8))
unzip
从包含pair的List中生成包含List的Pair。
listOf(Pair(5, 7), Pair(6, 8)).unzip()
顺序操作符
reverse
返回一个与指定list相反顺序的list。
val unsortedList = listOf(3, 2, 7, 5)
unsortedList.reverse()
sort
返回一个自然排序后的list。
unsortedList.sort()
sortBy
返回一个根据指定函数排序后的list。
unsortedList.sortBy { it % 3 }
sortDescending
返回一个降序排序后的List。
unsortedList.sortDescending()
sortDescendingBy
返回一个根据指定函数降序排序后的list。
unsortedList.sortDescendingBy {it % 3 }
asSequence()
每个操纵集合的函数都会新建一个临时集合以存放中间结果,为了更好的性能,就使用序列
val friends = list.asSequence()
.flatMap { listOf(it + 1) }
.filter { it > 3 }
.map {
it + 1
}
.toSet()
通过调用asSequence()将原本的集合转化成一个序列,序列将对集合元素的操作分为两类:
- 中间操作
- 末端操作
从返回值上看,中间操作返回的另一个序列,而末端操作返回的是一个集合(toSet()就是末端操作)。 从执行时机上看,中间操作都是惰性的,也就说中间操作都会被推迟执行。而末端操作触发执行了所有被推迟的中间操作。所以将toSet()移动到了末尾。 序列还会改变中间操作的执行顺序,如果不用序列,n 个中间操作就需要遍历集合 n 遍,每一遍应用一个操作,使用序列之后,只需要遍历集合 1 遍,在每个元素上一下子应用所有的中间操作。
类属性的延迟初始化
直接初始化为null
不推荐
// 初始化为null,但是每次都要做可空类型参数调用
private var tipView:TextView? = null
lateinit关键字
不推荐
// lateinit 关键字延迟初始化;但是使用时需要自己保证不为空,否则空指针异常
// lateinit 会让编译器忽略初始化,但是不支持Int等基本类型
private lateinit var tipView2:TextView
by lazy
// 使用lazy;在 tipView3 首次被访问时执行
private val tipView3:TextView by lazy {
findViewById<TextView>(R.id.tv_main)
}