kotlin学习笔记-扩展,空对象安全,高阶函数,内联函数,常用内联函数,集合操作,类属性的延迟初始化

225 阅读8分钟

扩展

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)
}