跳至主要內容

Kotlin 学习笔记 2

JI,XIAOYONG...大约 8 分钟

尾递归优化

把递归通过编译器转化为迭代,从而避免 Stack Overflow

“以时间换取空间”

普通递归:

调用函数之后,还需要使用其返回值供自己使用,即自身返回值依赖于下一级函数,一般是调用自身的代码后面,还有其他的代码要执行。

fun fun1(n: Int): BigInteger {

    if (n == 0) return BigInteger.valueOf(1L)

    return n.toBigInteger().times(fun1(n - 1))

}

尾递归:
调用自身之后,无需再返回当前函数,将处理结果以其他形式返回。
普通递归和尾递归都存在栈溢出风险(未优化前,例子中的函数计算 10000 到 100000 的阶乘时会溢出),kotlin 提供了一种尾递归优化的方法——tailrec,使得编译器在编译时将递归转化为迭代,从而避免栈溢出。

data class Result(var value: BigInteger = BigInteger.valueOf(1L))

//尾递归,tailrec 为 kotlin 中优化关键字
tailrec fun fun2(n: Int, m: Result) {
    if (n == 0) {
        m.value = m.value.times(BigInteger.valueOf(1L))
        return
    } else {
        m.value = m.value.times(n.toBigInteger())
        fun2(n-1,m)
    }
}

本例中传入fun2()Result实例保存了计算结果

sealed class 密封类

密封类的所有子类必须在一个文件 (xx.kt) 中,他的子类是有限的,所以当when()的时候不需要else

某种意义上他们像是一种enum class,只不过他的子类可以有多个实例。

Sealed classes are used for representing restricted class hierarchies, when a value can have one of the types from a limited set, but cannot have any other type. They are, in a sense, an extension of enum classes: the set of values for an enum type is also restricted, but each enum constant exists only as a single instance, whereas a subclass of a sealed class can have multiple instances which can contain state.

//sealed class
sealed class Player{

    class Play(var arg:String) : Player()

    object Stop : Player()
}

class p2():Player()

kotlin 抛出异常

@Throws(RemoteException::class)
fun getBookList():List<Book>

kotlin 中的泛型

out 协变,使用子类泛型的对象可以赋值给使用父类泛型的对象,相当于extend,用于方法的返回值(生产者)时使用

in 逆变,使用父类泛型的对象可以赋值给使用子类泛型的对象,相当于super,用于方法的参数(消费者)时使用

不变,当泛型即当消费者,又当生产者时,不用in或者out

fun main(args: Array<String>) {
    val from = arrayOf(1,2,4)
    val to = arrayOf(Any())

    copyArray(from,to)
}

fun copyArray(from: Array<out Any>, to: Array<in Int>) {
    //这里的 from 被 out 修饰,只能作为生产者调用 get 之类的方法,不能作为消费者调用 set 之类的方法
    //...
}

星号投射

你对类型参数一无所知,但仍然希望以安全的方式使用它。

安全的使用,则表示该类Group<T>满足

1.子类至少接收和父类一样范围的参数 >= ---> 父类入参为 Noting 不能安全写入

2.子类最多返回和父类一样范围的参数 <= ---> 父类出参为 Any? 可以安全读取

则有以下三种实现方式

in-out-star-projection-approaches
in-out-star-projection-approaches

其中:

Group<in Noting>fetch()方法一直返回Any?

Group<out Any?>T需要与实际的GroupT保持一致,否则会报错

Group<*> 既能insert正确返回对应的类型,也不用实时修改

object TClass {

    fun readIn(group: Group<in Nothing>) {
        val d = group.fetch()
    }

    fun readOut(group: Group<out Animal>) {
        val d = group.fetch()
    }

    fun read(group: Group<*>) {
        val d = group.fetch()
    }

}
interface Group<T : Dog> {
    fun insert(member: T): Unit
    fun fetch(): T
}

委托

委托是将重复出现的代码放到一个地方。

委托示意图
委托示意图
  • 类委托:
interface Interface {	fun a()	}
class A : Interface
class B(a: Interface) : Interface by a

这样 B 便可以将Interface中方法的实现委托给类A的对象a

  • 委托属性:

将同一类型的属性的getset方法放到一个地方实现,可以在加入其它操作

class Delegate {
    var name: String = ""

    operator fun getValue(clazz: Any?, property: KProperty<*>): String {
        println("get()")//其它操作
        return name
    }

    operator fun setValue(clazz: Any?, property: KProperty<*>, t: String) {
        println(" set()")
        name = t
    }
}

class ClassA {
    var name: String by Delegate()
    var age: String by Delegate()
}
  • 委托类的初始化函数:
fun <T> delegate(initializer: () -> T) = Delegate(initializer)

class MyClass1 {
    var name: String by delegate {
        println("MyClass1.name init")
        "MyClass1"
    }
}

class Delegate<T>(initializer: () -> T) {
    operator fun getValue(myClass1: T, property: KProperty<*>): String {
        println("$className get()")
        return name
    }

    operator fun setValue(myClass1: T, property: KProperty<*>, t: String) {
        println("$className set()")
        name = t
    }

    var name: String = ""
    var className = initializer()
}
  • Map 委托:

将类的属性名称和map中的key一一对应,从而将对于value赋值给属性

class ClassB(map: Map<String, Any>) {
    val name: String by map
    val age: Int by map
}
fun main() {
    val map = mapOf("name" to "shany",
            "age" to 18)
    val b = ClassB(map)
    print(b.name)//shayn
    print(b.age)//18
}
  • veroable

可以拦截赋值操作

class ClassB() {
   var name:String by Delegates.vetoable("ThisIsInitialValue"){
       property, oldValue, newValue ->
       return@vetoable false //返回 true 允许更改值,false 不允许更改
   }
}

中缀函数

需要满足三个条件:

  1. 成员函数或拓展函数
  2. 只有一个参数
  3. infix 声明
infix fun String.div(string: String):String{
    return this.replace(string,"")
}

使用:
val s = "bababbaab" div "a"

inline 内联函数

inline修饰的函数在被调用时将字节码动态插入到被到调用的地方。

inline修饰的函数的lambda参数如果运行在该函数内部的*子函数/其他环境*,则不允许这个 lambda 函数非局部返回(因为没有办法从该 子函数/其他环境 中直接退出 lambda 所在的外层函数),对于这种 lambda 函数需要添加**crossinline**修饰。

//**非局部返回**指从 lambda2 中执行 return 语句,推出的是整个 func()
inline fun func1(crossinline lambda1:()->Unit,  lambda2:()->Unit){
    val f = Runnable {
        lambda1()//不可以调用非局部返回,所以用 crossinline 修饰
    }

    lambda2()//可以调用非局部返回
}

Kotlin 也存在 Java 泛型所具有的类型擦除问题,为了优化该问题,inline 函数可以结合reified实现实体化类型参数

inline fun <reified T> isInstanceOf(value: Any) = value is T //在这里仍然可以知道 T 是什么类型的,所以可以执行 value is T
print(isInstanceOf<String>(""))//true

**原理:**内联函数会直接被插入到被调用的地方,而reified修饰的类型参数会保证将用户调用时写的类型String同时也写入到被调用的地方,如此便没有发生类型擦除。

coroutines 协程

协程可以看做是一个轻量级的 thread,他运行在线程当中,由用户控制,没有上下文切换的开销。

在 Android 中使用协程,特别是在 IO 操作及网络请求等需要根据耗时操作更新界面的需求时,可以将 IO 操作和界面操作串行,避免切换线程、回调嵌套等导致代码可读性查的问题。

如配合支持协程的 retrofit,我们可以将网络请求简化如下:

        launch(Dispatchers.Main) {
            //这里是主线程
            showProgressOnMainThread()
            val repos = withContext(Dispatchers.IO) {
                //这里是 UI 线程
                retrofitApi.getRepos("jixiaoyong").string()
            }
            //这里是主线程
            updateUIOnMainThread(repos)
        }

kotlin 协程需要单独添加依赖:

implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2'
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2" //Android可以再添加这个依赖,会有一些特殊方法

一个父协程总是等待所有的子协程执行结束,父协程被取消的时候,所有它的子协程也会被递归的取消。

协程中runBlockingcoroutineScope的区别

相同点:

依次执行内部代码,如果代码1是启动协程,那么启动该子协程后,继续执行代码1后面的代码直到最后一行(类似启动新线程,不会阻塞当前线程),然后再等待所有内部协程结束,才会退出。

不同点:

runBlockingopen in new window:会运行一个新的协程线程,并阻塞其所在线程,直到其内部所有协程/子协程执行完毕才会退出。设计用来以阻塞的方式执行协程代码,不应该在协程中使用。

coroutineScopeopen in new window:不会阻塞其所在线程,要在协程中使用,当其内部所有协程/子协程执行完毕才会退出。设计用来执行并行操作,一旦有子协程失败,则其他子协程都会被取消,整个代码块执行失败。

协程的思维导图

需要说明的是

Dispatchers.Unconfined 非受限,不会限定协程运行的线程,而是随环境切换

    launch(Dispatchers.Unconfined) {
        // main thread
        withContext(newSingleThreadContext("hello")){
            //hello thread
        }
        //hello thread
    }

协程局部变量

通过ThreadLocalThreadContextElementopen in new window,配合asContextElement("value")方法实现。和协程所在的线程没有关系。

直接修改ThreadLocal的值,会在切换协程的时候失效(会被改为切换到的协程所使用的的值),当再次切回本协程时,被重置为上一个通过asContextElement("value")方法更新的值或者null(如果没有指定)。

原理:启动和恢复时保存ThreadLocal在当前线程的值,并修改为 value,挂起和结束时修改当前线程ThreadLocal的值为之前保存的值

—— Kotlin Coroutines(协程) 完全解析(五),协程的并发open in new window

val threadLocal = ThreadLocal<String?>() // 声明线程局部变量

fun main() = runBlocking<Unit> {

    threadLocal.set("main")
    printValue(1) // main
    async (Dispatchers.Default + threadLocal.asContextElement(value = "launch")) {
        printValue(2) // launch
        threadLocal.set("hello")
//        threadLocal.asContextElement("hello") //如果使用这个方法更新,则 printValue(4) 会打印 hello
        printValue(3) // hello
        yield()
        printValue(4)// launch
    }.await()
    printValue(5) // main
}

fun printValue(number: Int){
    println("$number: ${Thread.currentThread()}, thread local value: '${threadLocal.get()}'")
}

在 Android 中使用

Kotlin 官方推荐一下两种方式:

1. CoroutineScope
class Activity : CoroutineScope by CoroutineScope(Dispatchers.Default) {
    // 继续运行……

2. MainScope
class Activity {
    private val mainScope = MainScope()
    fun destroy() {
    mainScope.cancel()
}
// 继续运行……

参考资料

Star-Projections and How They Workopen in new window

Kotlin 的独门秘籍 Reified 实化类型参数 (下篇)open in new window

Kotlin 协程官网open in new window

Kotlin Coroutines(协程) 完全解析(五),协程的并发open in new window

文章标题:《Kotlin 学习笔记 2》
本文作者: JI,XIAOYONG
发布时间: 2018/02/11 11:38:43 UTC+8
更新时间: 2023/12/30 16:17:02 UTC+8
written by human, not by AI
本文地址: https://jixiaoyong.github.io/blog/posts/8a52763a.html
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 许可协议。转载请注明出处!
你认为这篇文章怎么样?
  • 0
  • 0
  • 0
  • 0
  • 0
  • 0
评论
  • 按正序
  • 按倒序
  • 按热度
Powered by Waline v2.15.8