美文网首页Android Kotlin
写出优雅的Kotlin代码:聊聊我认为的 "Kotlinic"(

写出优雅的Kotlin代码:聊聊我认为的 "Kotlinic"(

作者: 我爱田Hebe | 来源:发表于2022-08-26 11:06 被阅读0次

    善用"="号

    这里的意思其实包含了两个方面:用好Kotlin表达式的返回值,以及"="号做返回值的函数

    Kotlin表达式返回值

    学过Kt的大家都知道,不同于Java,它的if,else,try这些是带返回值的。在很多地方,使用 = 加上一个表达式的返回值看着可能会更清晰一些。

    也就是,把类似于下面这种的“Java”式写法

    val weekday = 6 // 一周第几天,[1, 7]
    var dayDescription = ""
    when(weekday){
        in 1..5 -> dayDescription = "工作日"
        in 6..7 -> dayDescription = "周末"
        else -> dayDescription = "世界末日"
    }
    

    改成下面的“Kotlin”写法

    val weekday = 6 // 一周第几天,[1, 7]
    val dayDescription = when(weekday){
        in 1..5 -> "工作日"
        in 6..7 -> "周末"
        else -> "世界末日"
    }
    

    在try...catch的场合,这样的差异会更明显。比如一个非常简易的读文件例子(下面的代码仅可以读取小文件,请谨慎地实际使用)

    val filePath = "d://学习资料/日语资料.txt"
    var result = ""
    var inputStream: FileInputStream? = null
    try {
        inputStream = FileInputStream(File(filePath))
        result = inputStream.readBytes().decodeToString()
    }catch (e: Exception){
        result = "读取失败!"
        e.printStackTrace()
    }finally {
        try {
            inputStream?.close()
        }catch (e: IOException){
            e.printStackTrace()
        }
    }
    

    这是个典型的Java写法,用Kotlin的写法会简洁些

    val result = try {
        File(filePath).inputStream().use {
            it.readBytes().decodeToString()
        }
    }catch (e: Exception){
        e.printStackTrace()
        "读取失败"
    }
    

    这两处代码间主要有下面几处变化:

    • result的赋值直接由try...catch语句的返回值提供
    • 文件流的关闭由Closeable.use拓展函数内部处理,外部调用更简洁
    • 使用其他拓展函数避免了嵌套的对象创建

    如果你对中间读取的错误不关心,可以使用下面的形式

    val result = runCatching {
        File(filePath).inputStream().use {
            it.readBytes().decodeToString()
        }
    } .getOrDefault("读取失败")
    

    runCatching函数返回一个Result对象,其getOrDefault方法可以在出错时使用给定的默认值(其他几个get方法包括getOrNull、getOrThrow、getOrElse)。这样写起来更简洁
    不过,上面的写法还是不够Kotlin。借助Kt提供的琳琅满目的拓展函数,其实上面的代码可以写成这样

    val result = runCatching {
        File(filePath).readText()
    } .getOrDefault("读取失败")
    

    在不需要考虑buffer的情况下,流都不需要管啦 :)

    函数返回

    用"="写函数其实在官方的各种拓展函数里非常常见。有一点比较有趣的是,因为Unit在Kotlin里面也是一种普通的类型,所以即使函数什么也不返回(也就是返回Unit),也可以拿"="写。不过这一点就因人而异了,得看实际情况。

    对于一些非"unit"返回值的简单函数,用"="显得清晰明了

    比如上面的获取dayDescription

    fun getDayDescripton(weekday: Int) = when(weekday){
        in 1..5 -> "工作日"
        in 6..7 -> "周末"
        else -> "世界末日"
    }
    

    比如打log时可能要输个分割线

     // 重复字符串
    inline operator fun String.times(n: Int) = this.repeat(n)
    
    val divider = "=" * 20 // ******************** 
    

    比如上面的读短文本

    fun File.readText(default: String) = runCatching {
        this.readText()
    } .getOrDefault(default)
    

    类似的例子很多很多,就不赘述了。

    杂项

    Collection

    kt的集合可以说是很强大了,该有的不该有的它都给了。随便举几个例子吧

    创建

    我经常看到类似这样的代码

    val list = arrayListOf<int>()
    list.add(1)
    list.add(2)
    list.add(3)
    

    嗯,很Java。实际上创建一个列表,Kt有更好的方法。
    带初始参数的arrayListOf

    val list = arrayListOf(1, 2, 3)
    

    要是复杂一点呢?比如值为index的平方?

    val list = List(3) { i -> i*i } 
    

    基于其他对象创建?

    val names = students.map{ it.name }
    

    基于另一个列表中某些符合要求的创建?

    // 及格的同学们
    val passedStudents = students.filter{ it.grade >= 60 }
    

    转字符串

    比如:["a", "b", "c"] -> "a, b, c"

    val string = listOf("a","b","c").joinToString{ it }
    

    也可以设置前后缀、分隔符等

    val string = listOf("a","b","c").joinToString(prefix = "[", postfix = "]") { it }
    println(string) // [a, b, c] 
    

    Kt的Collection有很多很好用的方法,此处就不赘述了,大家感兴趣的自己翻翻源代码便是。

    代理

    Kotlin 的 by 应该说用的不少,它对应的概念“Delegate”也是语法上相较Java特别的地方。常见的用处呢,最简单的就是by lazy延迟初始化;除此之外,利用它也能快速实现一个“懒汉式”的单例(饿汉式的就object

    class DataManager {
        companion object {
            val IMPL by lazy {
                DataManager()
            }
        }
    }
    

    放Java的话,上述代码语义上类似于

    if(IMPL != null)return IMPL;
    synchronized(lock) {
        if(IMPL == null){
            IMPL = new DataManager();
        }
        return IMPL;
    } 
    

    如果不需要锁,还可以加上参数 lazy(LazyThreadSafetyMode.NONE)

    配合协程

    如果懒加载的内容是耗时操作,还可以配合上协程,实现异步的懒加载

     /*
     * 异步懒加载,by FunnySaltyFish
    *
    * @param T 要加载的数据类型
    * @param scope 加载时的协程作用域
    * @param block 加载代码跨
    * @return Lazy<Deferred<T>>
    */
    fun <T> lazyPromise(scope: CoroutineScope = MainScope(), block: suspend CoroutineScope.() -> T) =
        lazy {
            scope.async(start = CoroutineStart.LAZY) {
                block.invoke(this)
            }
        } 
    

    使用的时候才去加载数据,而且可以异步加载。
    比如

    private suspend fun fetchData() : String {
        println("开始加载数据")
        delay(1000)
        println("加载完毕")
        return "成功"
    }
    
    val username by lazyPromise(viewModelScope) {
        fetchData()
    }
    val password by lazyPromise(viewModelScope) {
        fetchData()
    } 
    

    而具体使用这两个变量的方法为

    suspend fun login() = withContext(Dispatchers.IO) {
        username.start()
        password.start()
        println("${username.await()} ${password.await()} 登陆成功!")
    } 
    

    最后在调用这个函数(比如点击事件)时

    onClick = {
        scope.launch {
            viewModel.login()
        }
    } 
    

    没有值时会去异步加载这个值,输出如下:

    开始加载数据
    开始加载数据
    加载完毕
    加载完毕
    成功 成功 登陆成功!
    

    后面再调用就直接使用已经初始化好的值,输出如下

    成功 成功 登陆成功!
    

    代理属性

    代理的一个常见用法估计就是代理各种东东了
    Map

    class People(val map: Map<String, Any?>){
        val name: String by map
        val age: Int by map
    }
    
    val people = People(mapOf("name" to "FunnySaltyFish", "age" to 20))
    println("${people.name}: ${people.age}") // FunnySaltyFish: 20
    

    Intent

    // 接收另一个activity传来的数据
    val fromEntrance by intentData<String>("entrance")
    

    数据库

    // User是某个数据库的表,name age是两列
    val name by User.name
    val age by User.age
    

    至此本文也差不多结束了,林林总总写了我一个多星期,感觉也是一个挺奇妙的过程。最后,如果你觉得我的内容还不错的话,欢迎点个赞,这对我帮助很大!

    作者:FunnySaltyFish
    链接:https://juejin.cn/post/7125992160691748871

    相关文章

      网友评论

        本文标题:写出优雅的Kotlin代码:聊聊我认为的 "Kotlinic"(

        本文链接:https://www.haomeiwen.com/subject/ugytnrtx.html