秒懂Kotlin之轻松掌握Scope Functions (apply, also,let,run,with)

2023-11-25 16:10

本文主要是介绍秒懂Kotlin之轻松掌握Scope Functions (apply, also,let,run,with),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

[版权申明] 非商业目的注明出处可自由转载
博文地址: https://blog.csdn.net/ShuSheng0007/article/details/108776472
出自:shusheng007

文章首发于个人博客

文章目录

  • 概述
  • 定义
  • 使用方法
    • 合适才是最好的
  • 分类
    • 相同之处
    • 不同之处
      • 作用域中的上下文对象不同
      • 返回值不同
  • 使用场景
  • 总结

概述

Kotlin和Java相比提供了很多语法糖,其目的当然是为了提高程序员的编码效率,但是其中一些过于灵活导致增加了其正确使用的难度,其中 Scope Functions 就属于这一类。曾几何时,面对 applyalsoletrun还有with是不是傻傻分不清?不要羞愧,因为你不是一个人!(千万不要问:那我是什么…? 如果你非要这么问,我想大概是一只程序吧)

定义

Scope Functions 是指下表中列出的5个函数,那他们为什么叫scope functions呢?

名称定义
applypublic inline fun T.apply(block: T.() -> Unit): T
alsopublic inline fun T.also(block: (T) -> Unit): T
letpublic inline fun <T, R> T.let(block: (T) -> R): R
runpublic inline fun <T, R> T.run(block: T.() -> R): R
withpublic inline fun <T, R> with(receiver: T, block: T.() -> R): R

以上5个函数均提供了一种能力,是什么呢?就是他们都会构建一个临时的作用域(scope),在此作用域里我们可以不通过对象的名称而访问此对象,所以他们都叫scope函数。

让我们实际来体会一下。需求:构建一个Student对象,然后设置其姓名和年龄

Java实现:

Student student = new Student();
student.setName("ShuSehng007");
student.setAge(18);

Kotlin实现:

 val student: Student = Student().apply {name = "ShuSheng007"age = 18}

在Java中要访问student对象,需要使用其名称student。但是当使用Kotlin的apply函数后,其构建了一个临时的作用域,在此作用域内访问student对象就不需要其名称了,就像上面{}内的代码展示的一样。不可否认其在代码在逻辑上变得确实更紧凑了。

使用方法

关于他们的使用方法,网上有小伙伴总结了一副图,很是全面,在此致敬一下。
在这里插入图片描述
虽然此图对Scope Functions的使用场景总结的很到位,但是我仍然坚信理解其背后的原理才是优秀程序员碾压菜鸟程序员的不二法宝。

Scope Functions 之所以让人迷惑,就是因为他们之间太像了,不仅长得像,功能上也像,同一个需求使用他们任何一个都可以完成。这就和你问你媳妇今晚想吃什么的时候,她漫不经心的回答到:都行,随便!是一样一样的。

这不王二狗刚刚问牛翠花:翠花,你看这个需求使用哪个scope函数实现啊? 翠花悠悠地说道:都行,随便!

你是二狗你咋办,关键是人家翠花说的对,不信请看下面的代码,打印出"ss007"这个字符串的长度:

fun runScopeFunctions(){val str="ss007"val size1: Int = str.apply {println(this)}.lengthval size2: Int = str.also {println(it)}.lengthval size3: Int= str.let {println(it)it.length}val size4: Int= str.run {println(this)this.length}val size5: Int= with(str){println(this)this.length}val size6: Int = run {println(str)str}.length
}

人家翠花不仅用5个scope函数证明了自己,顺便还送了你一个run的非scope函数用法,这小娘子是要上天啊,必须的治一治了。

合适才是最好的

那是不是真的就意味着这些scope functions可以不加区分的互用了呢?当然不是,不然要5个干啥,一个就足够了!以后你媳妇再说:都行,随便。你就说:好的,晚上给你做《冷水拌米糠》。

你看不管是山珍海味,还是冷水拌米糠都能吃饱,但是你想吃冷水拌米糠吗?就是这个道理,我们都是有追求的人,所以我们要使用最合适的方法,写出最优雅的code。。。

有的小朋友可能不耐烦了:行了,别TM嘚嘚啦,快点上干货吧!小朋友稍安勿躁,俺小时候的愿望是当小说家,长大后不幸做了程序员,不幸中的万幸是程序员可以写博客… 呀,再哔哔下去确实有点跑题,那就让我们开始接着上干货吧。

分类

对于相似的事物,掌握它们的最好办法就是按照他们之间的异同进行分类,求同存异,此处也不例外。

相同之处

  1. 均为内联(Inline)函数
    意味着:无性能损失
  2. 除了with以外,其余4个均只有一个函数类型的入参
    意味着:可以使用如下语法 T.xxx{ }
    "ss007".also{println(it)
    }	
    
  3. 除了with 其他4个均为泛型扩展函数

不同之处

从scope functions的定义上可以看出,这5个函数的区别主要表现在两个维度上。

  • 作用域中的上下文对象不同
  • 函数返回值不同

其中with的定义比较特殊,它是唯一一个不是扩展函数的Scope Function,但是区别仅仅是将receiver当参数传入。如下代码,runreceiver写在外边,而withreceiver写在里面,除此之外完全相同。

    val size4: Int= str.run {println(this)this.length}val size5: Int= with(str){println(this)this.length}

作用域中的上下文对象不同

  • 需要使用it访问上下文对象
public inline fun <T, R> T.let(block: (T) -> R): R
public inline fun <T> T.also(block: (T) -> Unit): T 

从上面的定义可以发现:letalso 入参类型为(T) -> R(T) -> Unit,即可以传入一个带一个类型为T的参数的 Lambda表达式,而单参数在Lambda表达式中隐含使用it表示,如下代码所示

str.also {println(it)}
str.let {println(it)
}
  • 需要使用this访问上下文对象
public inline fun <T> T.apply(block: T.() -> Unit): T 
public inline fun <T, R> T.run(block: T.() -> R): R
public inline fun <T, R> with(receiver: T, block: T.() -> R): R

从上面的定义可以看出,applyrunwith最后一个 入参类型为T.() -> UnitT.() -> R

看到T.()是不是又懵逼了?这玩意叫 “Kotlin Function Literals with Receiver” ,是不是还有点懵逼?让我们将概念化繁为简,其直接翻译为:带接收器函数字面量。由于Lambda是一种函数字面量,所以其可以进一步具体化为:带接收器的Lambda表达式。

例如有如下Lambda

val greet1:()->Unit= { println("hello world")}

我们可以为上面的lambda加上一个String类型的receiver,使其变成下面这样

var greet2: String.() -> Unit = { println("Hello $this") }

我们可以在{}中以this访问这个receiver。值得注意的是,greet2有两种等价的执行方法

greet2("world")
"world".greet2()

返回值不同

  • 返回receiver
public inline fun <T> T.apply(block: T.() -> Unit): T 
public inline fun <T> T.also(block: (T) -> Unit): T 

可见返回类型为T,与receiver的类型相同

  • 返回Lambda表达式的值
public inline fun <T, R> T.let(block: (T) -> R): R
public inline fun <T, R> T.run(block: T.() -> R): R
public inline fun <T, R> with(receiver: T, block: T.() -> R): R

可见返回类型为R,与传入的lambda的类型相同

使用场景

以上就是scope函数的异同,所以说要使用哪个完全要根据你的需求和偏好决定,所以才是最难的。Java为什么如此适合长期的大型项目,就是因为它死板,或者说严谨,谁来了都得这么写,这就达到了后来人可以轻松看得懂的效果,维护扩展起来才相对容易,才更有可能达到巨大的规模。

下面是官方列出的一些使用场景:

  • let

在一个非null对象上执行lambda表达式

obj?.let{ }

声明一个局部表达式变量

val v= obj?.let{ }
  • apply

设置对象

val student: Student = Student().apply {name = "ShuSheng007"age = 18}
  • run

设置对象并计算其结果

val studentInfo: String = Student().run {name = "ShuSheng007"age = 18        "My name is $name and I amm $age "}

构建一个可执行的表达式块

val runBlock= run {"ss007"
}
for (c in runBlock.chars()){println(c)
}

其和Lambda函数字面量还是有一点区别的,其可以直接执行,不需要使用()调用语法。下面是Lambda版本,注意看起执行的话需要lambdaBlock()语法,后面有()

val lambdaBlock :()->String = {"ss007"
}for (c in lambdaBlock().chars()){println(c)
}
  • also

追加行为,例如打印日志

val student = Student().also {println("Student对象成功创建了:${it.toString()}")}
  • with

将在同一个对象上的方法调用group起来

 with(mutableListOf("shu", "sheng")) {add("007")println(fold("我们都爱") { result, element -> result + element })}

输出结果

我们都爱shusheng007

根据返回类型及上下文对象的访问方式谨慎选择最合适的scope函数,一切以提高可读性为准,切记不可滥用!

总结

Kotlin 就像个刚进门的多才多艺的小妾,使用各种奇技淫巧把老爷伺候的欲仙欲死,还一直挑衅正房Java。

老爷: Java姐姐会的妾身都会,Java姐姐不会的,妾身也会,老爷你看这招:协程,爽不爽啊?你要是觉得一时不习惯没有java姐姐的日子,妾身可以同时和Java姐姐一起伺候你,我们是100%互通的,可以和谐共处…

不知道你们怎么想,我反正是想试试kotlin,建议你也试一试。。。又到了点赞的时候了,抬起陪伴你多年的右手,对准那个大拇指猛戳一下即可

自古逢秋悲寂寥,我言秋日胜春朝。——刘禹锡《秋词》

这篇关于秒懂Kotlin之轻松掌握Scope Functions (apply, also,let,run,with)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/424101

相关文章

闲置电脑也能活出第二春?鲁大师AiNAS让你动动手指就能轻松部署

对于大多数人而言,在这个“数据爆炸”的时代或多或少都遇到过存储告急的情况,这使得“存储焦虑”不再是个别现象,而将会是随着软件的不断臃肿而越来越普遍的情况。从不少手机厂商都开始将存储上限提升至1TB可以见得,我们似乎正处在互联网信息飞速增长的阶段,对于存储的需求也将会不断扩大。对于苹果用户而言,这一问题愈发严峻,毕竟512GB和1TB版本的iPhone可不是人人都消费得起的,因此成熟的外置存储方案开

【数据结构】——原来排序算法搞懂这些就行,轻松拿捏

前言:快速排序的实现最重要的是找基准值,下面让我们来了解如何实现找基准值 基准值的注释:在快排的过程中,每一次我们要取一个元素作为枢纽值,以这个数字来将序列划分为两部分。 在此我们采用三数取中法,也就是取左端、中间、右端三个数,然后进行排序,将中间数作为枢纽值。 快速排序实现主框架: //快速排序 void QuickSort(int* arr, int left, int rig

生信代码入门:从零开始掌握生物信息学编程技能

少走弯路,高效分析;了解生信云,访问 【生信圆桌x生信专用云服务器】 : www.tebteb.cc 介绍 生物信息学是一个高度跨学科的领域,结合了生物学、计算机科学和统计学。随着高通量测序技术的发展,海量的生物数据需要通过编程来进行处理和分析。因此,掌握生信编程技能,成为每一个生物信息学研究者的必备能力。 生信代码入门,旨在帮助初学者从零开始学习生物信息学中的编程基础。通过学习常用

轻松录制每一刻:探索2024年免费高清录屏应用

你不会还在用一些社交工具来录屏吧?现在的市面上有不少免费录屏的软件了。别看如软件是免费的,它的功能比起社交工具的录屏功能来说全面的多。这次我就分享几款我用过的录屏工具。 1.福晰录屏大师 链接直达:https://www.foxitsoftware.cn/REC/  这个软件的操作方式非常简单,打开软件之后从界面设计就能看出来这个软件操作的便捷性。界面的设计简单明了基本一打眼你就会轻松驾驭啦

NGINX轻松管理10万长连接 --- 基于2GB内存的CentOS 6.5 x86-64

转自:http://blog.chinaunix.net/xmlrpc.php?r=blog/article&uid=190176&id=4234854 一 前言 当管理大量连接时,特别是只有少量活跃连接,NGINX有比较好的CPU和RAM利用率,如今是多终端保持在线的时代,更能让NGINX发挥这个优点。本文做一个简单测试,NGINX在一个普通PC虚拟机上维护100k的HTTP

如何掌握面向对象编程的四大特性、Lambda 表达式及 I/O 流:全面指南

这里写目录标题 OOP语言的四大特性lambda输入/输出流(I/O流) OOP语言的四大特性 面向对象编程(OOP)是一种编程范式,它通过使用“对象”来组织代码。OOP 的四大特性是封装、继承、多态和抽象。这些特性帮助程序员更好地管理复杂的代码,使程序更易于理解和维护。 类-》实体的抽象类型 实体(属性,行为) -》 ADT(abstract data type) 属性-》成

JAVA初级掌握的J2SE知识(二)和Java核心的API

/** 这篇文章送给所有学习java的同学,请大家检验一下自己,不要自满,你们正在学习java的路上,你们要加油,蜕变是个痛苦的过程,忍受过后,才会蜕变! */ Java的核心API是非常庞大的,这给开发者来说带来了很大的方便,经常人有评论,java让程序员变傻。 但是一些内容我认为是必须掌握的,否则不可以熟练运用java,也不会使用就很难办了。 1、java.lang包下的80%以上的类

JAVA初级掌握的J2SE知识(一)

时常看到一些人说掌握了Java,但是让他们用Java做一个实际的项目可能又困难重重,在这里,笔者根据自己的一点理解斗胆提出自己的一些对掌握Java这个说法的标准,当然对于新手,也可以提供一个需要学习哪些内容的参考。另外这个标准仅限于J2SE部分,J2EE部分的内容有时间再另说。 1、语法:必须比较熟悉,在写代码的时候IDE的编辑器对某一行报错应该能够根据报错信息知道是什么样的语法错误并且知道

作为刚从事Java开发的小白,需要掌握哪些技能

作为一个刚踏入Java开发世界的小白,面对各种技术和工具,你可能会觉得有点不知所措。但是别担心,我会给你一个简单清晰的路线图,让你可以有条不紊地掌握基本技能,逐步成长为一名Java开发者。 1. 扎实的Java基础 Java的基础是你迈向高级开发的重要基石,建议从以下几个方面着手: 语法和基础概念:比如变量、条件语句、循环、方法、数组、面向对象编程(OOP)等等。这些基础如同建房子的地基,越

【轻松上手postman】入门篇:如果根据接口文档写postman接口用例

在我们平时的测试工作中除了最基本的网页测试外,也会遇到没有页面但需要验证内部逻辑正确性的接口测试任务,在遇到没有网页的测试任务时,我们就要使用到接口测试工具来模拟对程序代码触发。 在接到接口测试任务时,一般都会拿到接口需求文档,没接触过接口测试的人看到接口文档正常反应一脸闷🤣不知如何下手怎么开始测试😓,下面我就来讲讲如何将接口文档上的一个个接口转换成postman用例 首先需要安装