草庐IT

kotlin高级特性

JakeWharton 2023-09-21 原文

Kotlin语法的高级特性异常强大,代码异常简洁,如果你在项目中能熟练使用各种kotlin高级特性后,你会发现,你之前这些年写的代码都是在浪费生命。

标准函数

kotlin的标准函数,指的是Standard.kt文件中定义的函数,包括let、also、with、run、apply函数。

  • let函数

let扩展函数的实际上是一个作用域函数,当你需要去定义一个变量在一个特定的作用域范围内,let函数的是一个不错的选择;let函数另一个作用就是可以避免写一些判断null的操作。

适用场景
场景一: 最常用的场景就是使用let函数处理需要针对一个可null的对象统一做判空处理。

        //没有let函数,需要每次判空,代码不够优雅
         data?.toString()
        data?.toString()
        data?.toString()

data?.let {
            //在函数域中,保证data对象不为,不用多次去判空
            it.toString()
            it.toString()
            it.toString()
        }

场景二: 然后就是需要去明确一个变量所处特定的作用域范围内可以使用

data.let {
            //在函数体内使用it替代该data对象使用
            it.toString()
        }
also函数

also函数使用的一般结构

object.also{

}

适用于let函数的任何场景,also函数和let很像,只是唯一的不同点就是let函数最后的返回值是最后一行的返回值而also函数的返回值是返回当前的这个对象。

        user?.also {
            it.age = 18
            it.name = "小明"
        }.age
with函数

with函数使用的一般结构

with(object){
   //todo
 }

它是将某对象作为函数的参数,在函数块内可以通过 this 指代该对象。

适用场景
需要设置某个对象多个属性到UI上时,需要不断调用对象,使用with函数能避免对象的重复书写。

        data class User(var name: String, var age: Int)

        with(user){
            Log.i("TAG", "age=$age")
            Log.i("TAG", "name=$name")
        }

该age和name变量就是该with参数中的user对象的属性值。

run函数

run函数使用的一般结构

object.run{
    
}

适用场景
适用于let,with函数任何场景。因为run函数是let,with两个函数结合体,准确来说它弥补了let函数在函数体内必须使用it参数替代对象,在run函数中可以像with函数一样可以省略,直接访问实例的公有属性和方法,另一方面它弥补了with函数传入对象判空问题,在run函数中可以像let函数一样做判空处理。

        user?.run {
            Log.i("TAG", "age=$age")
            Log.i("TAG", "name=$name")
        }
apply函数

apply函数使用的一般结构

object.apply{

}

从结构上来看apply函数和run函数很像,唯一不同点就是它们各自返回的值不一样,run函数是以闭包形式返回最后一行代码的值,而apply可以任意调用该对象的任意方法,并返回该对象。

适用场景
整体作用功能和run函数很像,唯一不同点就是它返回的值是对象本身,而run函数是一个闭包形式返回,返回的是最后一行的值。

场景一:apply一般用于一个对象实例初始化的时候,需要对对象中的属性进行赋值。

data class User(var name: String, var age: Int)

ArrayList<User>().apply {
            add(User("小明", 18))
            add(User("小王", 16))

        }.run {
            get(0)
        }       

创建User对象并添加入集合,添加完成后取出第一个对象。

场景二:多层级判空问题

    data class Response(var code: Int, var data: Data)

    data class Data(var icon: String, var title: String)

        response?.apply {
            //response不为空时可操作response
            
        }.data?.apply {
            //data不为空时可操作data
            
        }.title?.apply {
            //title不为空时可操作title
            
        }
let,with,run,apply,also函数总结:
函数名 函数体内使用的对象 返回值 适用场景
let it指代当前对象 返回任意类型R 适用于处理多处判空的场景
also it指代当前对象 返回this 适用于let函数的任何场景,一般可用于多个扩展函数链式调用
with this指代当前对象或者省略 返回任意类型R 适用于调用同一个类的多个方法、属性时,省去每次书写类名
run this指代当前对象或者省略 返回任意类型R 适用于let,with函数任何场景
apply this指代当前对象或者省略 返回this 适用于run函数任何场景,一般可用于多个扩展函数链式调用
takeIf,takeUnless函数
        user.name.takeIf {
            TextUtils.isEmpty(it)
        }.let {
            print(it)
        }

        user.name.takeUnless {
            !TextUtils.isEmpty(it)
        }.let {
            print(it)
        }

takeIf的闭包返回一个判断结果,如果为false时takeIf函数返回null;takeUnless与takeIf相反,为true时takeUnless函数返回null。

使用场景:只需要单个if分支语句的时候
优点:
可以配合其他作用域函数返回的结果,做出单向判断,保持链式调用
简化写法,逻辑清晰,减少代码量,代码更优雅

Kotlin委托

委托是一种设计模式,它的基本理念是:操作对象自己不会去处理某段逻辑,而是会把工作委托给另外一个辅助对象去处理。 比如调用A类的methodA方法,其实背后是B类的methodB去执行。

Kotlin将委托功能分为了两种:类委托和委托属性。

  • 类委托:即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
interface User {
    fun login()
}

class UserImpl(val name: String) : User{
    override fun login() {
        println(name)
    }
}

class VipUser(user: User) : User by user

fun main() {
    VipUser(UserImpl("1号用户")).login()
}

可以看到委托类并没有实现User接口,而是通过关键字by,将实现委托给了user。

  • 属性委托:委托属性的核心思想是将一个属性(字段)的具体实现委托给另一个类去完成。
class MyClass {
  var p by Delegate()
}

class Delegate {
  var propValue: Any? = null
  
  operator fun getValue(myClass: MyClass, prop: KProperty<*>): Any? {
    return propValue
  }
  
  operator fun setValue(myClass: MyClass, prop: KProperty<*>, value: Any?) {
    propValue = value
  }   
}

这里使用by关键字连接了左边的p属性和右边的Delegate实例,这种写法就代表着将p属性的具体实现委托给了Delegate类去完成。当调用p属性的时候会自动调用Delegate类的getValue()方法,当给p属性赋值的时候会自动调用Delegate类的setValue()方法。

by lazy并不是连在一起的关键字,只有by才是Kotlin中的关键字,lazy在这里只是一个高阶函数而已,返回的实例可以作为实现延迟属性的委托。

val lazyProp: String by lazy {
    println("Hello,第一次调用才会执行我!")
    "Hello"
}
// 打印lazyProp 3次,查看结果
fun main() {
    println(lazyProp)
    println(lazyProp)
    println(lazyProp)
}

打印结果如下:

Hello,第一次调用才会执行我!
Hello
Hello
Hello

可以看到,只有第一次调用,才会执行lambda表达式中的逻辑,后面调用只会返回lambda表达式的最终值。

参考:
https://blog.csdn.net/xingyu19911016/article/details/126414296
https://mp.weixin.qq.com/s/QQy2zPcmtUL3SlRHMNWBEQ

扩展函数

扩展函数表示即使在不修改某个类的源码的情况下,我们仍然可以对某个类添加方法,进行扩展。例如我们对User类增加登录的功能,实现在类外面添加方法。

    fun User.login() {
        Log.i("TAG","去登录")
    }

 data class User(var name: String, var age: Int)

扩展了login方法,就可以使用user.login()。可以看到,扩展函数的主要写法就是在定义方法名的时候,通过Class.直接声明是在哪个类中。

集合操作符

Kotlin中可以通过集合操作符直接对集合进行操作,从而得到想要的结果。

map:对集合中的数据做改变,可以改变数据的类型。
filter:得到所有符合Lambda闭包中操作的数据。
find:得到符合Lambda闭包中操作的第一个数据。
findLast:得到符合Lambda闭包中操作的最后一个数据。
reduce:含有两个参数(集合中相邻的两个参数,用于遍历整个集合),对这两个参数进行操作,返回一个新参数,要求类型与集合中的参数类型相同。

协变与逆变

我们约定,在一个泛型类或者泛型接口的方法中,它的参数列表是接收数据的地方,就称为in位置,他的返回值是输出数据的地方,就称为out位置。

fun <T>test(param: T):T {
    return param
}

如上test方法中,传入泛型T的位置为in位置,返回类型T的位置为out位置。

泛型的协变:假如定义了一个MyClass<T>的泛型类,其中A是B的子类型,同时MyClass<A>又是MyClass<B>的子类型,我们就可以称MyClass在T这个泛型是协变的。如果泛型都是只读的(泛型加上out关键字),就能实现MyClass<A>是MyClass<B>的子类型
泛型的逆变:假如定义了一个MyClass<T>的泛型类,其中A是B的子类型,同时MyClass<B>又是MyClass<A>的子类型,我们就可以称MyClass在T这个泛型是逆变的。如果泛型都是只写的(泛型加上in关键字),就能实现MyClass<B>是MyClass<A>的子类型

协程

见我的Kotlin协程使用

DSL(domain specific language)

即领域专用语言:专门解决某一特定问题的计算机语言,比如大家耳熟能详的 SQL 和正则表达式。

通用编程语言 vs DSL
通用编程语言(如 Java、Kotlin、Android等),往往提供了全面的库来帮助开发者开发完整的应用程序,而 DSL 只专注于某个领域,比如 SQL 仅支持数据库的相关处理,而正则表达式只用来检索和替换文本,我们无法用 SQL 或者正则表达式来开发一个完整的应用。

参考

Kotlin系列之let、with、run、apply、also函数的使用
Kotlin协程
Kotlin之美——DSL篇
一篇文章搞定kotlin

有关kotlin高级特性的更多相关文章

  1. ruby - 高级语言是否使用数据结构? - 2

    我目前还在上学,正在上一门关于用C++实现数据结构的类(class)。在业余时间,我喜欢使用“高级”语言(主要是Ruby和一些c#)进行编程。既然这些高级语言为你管理内存,你会用数据结构做什么?我可以理解对队列和堆栈的需求,但是您需要在Ruby中使用二叉树吗?还是2-3-4树?为什么?谢谢。 最佳答案 Sosincethesehigherlevellanguagesmanagethememoryforyou,whatwouldyouusedatastructuresfor?使用数据结构的主要原因与垃圾收集无关。但它是以某种方式有效的

  2. c# - 与 C# 相比,您会强调 Ruby 的哪些语言特性? - 2

    我正在就Ruby语言和环境向.NET(C#)开发团队进行一系列演讲。我把它当作一个机会来强调Ruby相对于C#的优势。首先,我想在进入环境之前专注于语言本身(RoR与ASPMVC等)。你会介绍Ruby语言的哪些特性? 最佳答案 我刚才在一个.NET用户组做了一个关于IronRuby的演讲,遇到了类似的问题。我关注的事情是:鸭子打字。没有什么比ListstringList=newList()更愚蠢的了;表达力强,语法简洁。简单的事情,比如省略括号、数组和散列文字等(结合鸭子类型,你会得到string_list=[]这显然更好)。所有的

  3. ruby - 你最喜欢 Ruby 的什么特性? - 2

    就目前而言,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引起辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter为指导。9年前关闭。已向.NET提出类似问题和Java,但不适用于Ruby。所以,你最喜欢Ruby的什么特性?您可能还对hiddenfeaturesofRuby感兴趣.请具体说明,并为每个答案发布一项功能。解释或代码示例会很好。 最佳答案 块非常好:my_array.each{|element|printelement}#.

  4. ruby-on-rails - Ruby on Rails 最酷的特性是什么,为什么选择它? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭8年前。Improvethisquestion在我问这个问题之前,我浏览了SO上“RubyonRails”的搜索结果。找不到太多,但以下(foundonthispage)让我觉得很有趣Personally,Istartedusing.html,movedontophp,triedruby(hatedit),discoveredPython/DJango..andhavebeenhappyeversince.这就是交易。我个人目前还没有

  5. ruby - 是否有任何你避免使用的 Ruby 语言特性? - 2

    在我看来,Ruby的句法非常灵活,很多东西可以用多种方式编写。作为Ruby程序员,有没有什么语言特性/语法糖/编码约定是您避免清楚的?我问的是您选择有意不使用的东西,而不是您仍然需要学习的东西。如果您的回答是“我什么都用!”,如果读者知道相关的Ruby语法,您是否曾经对代码进行注释?[我对RoR上下文中的Ruby特别感兴趣,但欢迎一切。] 最佳答案 “$”全局变量的整个范围(参见Pickaxe2pp333-336)大多继承自Perl,非常可怕,尽管我有时发现自己使用$:而不是$LOAD_PATH.

  6. ruby-on-rails - 有哪些对 Rails 开发有用的 Emacs 特性 - 2

    哪些Emacs功能、包、附加组件等可以帮助您进行日常的RubyOnRails开发? 最佳答案 两者的先前版​​本emacs-rails模式,和Rinari(Rails开发的两种最流行的模式)功能非常丰富,但又臃肿又笨重。为了保持一个小巧、干净、可靠、功能强大且可破解的核心,Rinari将避免许多“花里胡哨”的功能。然而,这并不是说这些额外的好东西可能没有用。这个页面应该作为链接到一些其他工具/包的链接点,这些工具/包通常与Rinari和Rails一起工作。如果您对添加到此列表或Rinari的新功能有任何想法,请通过http://gr

  7. ruby-on-rails - 在哪里可以找到高级 Ruby on Rails 教程? - 2

    按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我正在创建Rails应用程序,想知道在哪里可以找到好的有关如何使用Rails的教程。我使用了这个我认为非常适合开始学习Rails的博客:http://fairleads.blogspot.com/2007/12/rails-20-and-scaffolding-step-by-step.html我刚刚开始使用Rails,现在想学习更高级的Rails。

  8. Elasticsearch7.8.0版本入门—— 分页查询文档(高级查询) - 2

    目录一、初始化文档数据二、分页查询文档2.1、概述2.2、示例一、初始化文档数据在Postman中,向ES服务器发POST请求:http://localhost:9200/user/_doc/1,请求体内容为:{"name":"zhangsan","age":20,"sex":"男"}在Postman中,向ES服务器发POST请求:http://localhost:9200/user/_doc/2,请求体内容为:{"name":"zhangsan1","age":21,"sex":"男"}在Postman中,向ES服务器发POST请求:http://localhost:9200/user/_d

  9. 《MySQL高级篇》九、数据库的设计规范 - 2

    文章目录1.为什么需要数据库设计2.范式2.1范式简介2.2范式都包括哪些2.3键和相关属性的概念2.4第一范式(1stNF)2.5第二范式(2ndNF)2.6第三范式(3rdNF)2.7小结3.反范式化3.1概述3.2应用举例3.3反范式的新问题3.4反范式的适用场景4.BCNF(巴斯范式)5.第四范式6.第五范式、域键范式7.实战案例7.1迭代1次:考虑1NF7.2迭代2次:考虑2NF7.3迭代3次:考虑3NF7.4反范式化:业务优先的原则8.ER模型8.1ER模型包括哪些要素?8.2关系的类型8.3建模分析8.4ER模型的细化8.5ER模型图转换成数据表9.数据表的设计原则10.数据库对

  10. ruby - Scala 缺少哪些动态语言(如 Ruby 或 Clojure)的特性? - 2

    当您选择Scala(或F#、Haskell、C#)等静态类型语言而不是Ruby、Python、Clojure、Groovy(具有宏或运行时元编程功能)等动态类型语言时,您在实践中失去了什么)?请考虑最好的静态类型语言和最好的(在您看来)动态类型的语言,而不是最差的。答案总结:恕我直言,Ruby等动态语言相对于Scala等静态类型语言的主要优势是:快速的编辑-运行周期(JavaRebel是否缩小了差距?)目前Scala/Lift社区比Ruby/Rails或Python/Django小得多可以修改类型定义(尽管动机或需要不是很清楚) 最佳答案

随机推荐