Kotlin中标准函数run、with、let、also与apply的使用和区别详解

前言

和Java相比,在Kotlin中提供了不少新的特性。这次我们就来聊一聊Kotlin的一些通用的扩展标准函数run,with,let,also和apply。对于这五个标准函数它们都存在于Kotlin的源码标准库当中,也就是在Standard.kt文件当中。它们都是适用于任何对象的通用扩展函数。但是对于run,with,let,also和apply这五个函数他们的用法及其相似,以至于我们无法确定去选择使用哪一个。那么现在我们就来聊一下这五个函数它们的使用方法,它们的不同之处以及在什么场景下去使用。

作用域函数

在这里我们重点是看一下run,with,T.run,T.let,T.also,和T.apply,对于这几个函数来说它们最重要的功能之一是在调用函数的内部又提供了一个作用域。

那么下面就通过一段代码来看一下run函数的作用域,对于其它函数来说当然也是类似。

fun test(){
 var animal = "cat"
 run {
  val animal = "dog"
  println(animal) // dog
 }
 println(animal)  //cat
}

在这个简单的test函数当中我们拥有一个单独的作用域,在run函数中能够重新定义一个animal变量,并且它的作用域只存在于run函数当中。

目前对于这个run函数看起来貌似没有什么用处,但是在run函数当中它不仅仅只是一个作用域,他还有一个返回值。他会返回在这个作用域当中的最后一个对象。

例如现在有这么一个场景,用户领取app的奖励,如果用户没有登录弹出登录dialog,如果已经登录则弹出领取奖励的dialog。我们可以使用以下代码来处理这个逻辑。

run {
 if (islogin) loginDialog else getAwardDialog
}.show()

可以看到上面这段代码会变得更加的简洁,并且可以将show方法一次应用到上面两个dialog当中,而不是去调用两次。

with和其它通用标准函数

在这里之所以将with函数单独拿出来进行说明,是因为with得用法和其它通用的标准函数的用法比较独特。在这里我们依然使用run函数来进行对比。对于下面这段代码做的是同样一件事。它们的不同之处就是一个使用了with(T)函数,而另一个则是使用了T.run函数。

with(webView.settings){
 javaScriptEnabled = true
 databaseEnabled = true
}
webView.settings.run {
 javaScriptEnabled = true
 databaseEnabled = true
}

但是我们觉得使用哪一个会更好呢?现在假设一种场景,那就是webView.settings可能为null。那我们就来再次看一下下面这段代码.

with(webView.settings){
 javaScriptEnabled = true
 databaseEnabled = true
}
webView.settings?.run {
 javaScriptEnabled = true
 databaseEnabled = true
}

这么以来就很明显了,当然是T.run方法会更好,因为我们可以在使用这些函数之前可以进行对null的检查。

对于with也是存在一个返回值,它也是会返回在这个作用域当中的最后一个对象。

作用域中接收者this和it

在这几个扩展函数当中,它们都能直接获取到调用的对象或者是with中传入参数的对象。在这五个扩展函数在它们的作用域中的接收者可以是this或者是it。那么我们来对比一下T.run和T.let函数。这两个函数也是十分的相似。

stringVariable?.run {
 println("字符串的长度为$length")
}

stringVariable?.let {
 println("字符串的长度为 ${it.length}")
}

在这两段代码中可以清晰的看到。在T.run函数中通过this来获取stringVariable对象,而在T.let函数中通过it来取出stringVariable对象。当然我们也能够为it重新命名。如果我们不想覆盖外部作用域的this,这时候去使用T.let会更加的方便。至于哪些函数的接收者是this,哪些函数的接收者是it,在后面会通过一张树状图清晰的体现出来。

在作用域中返回值的类型

在这些作用域中它们都会存在一个返回值。在上面的讲述的run,with,T.run,T.let中它们返回的都是作用域中最后一个对象。当然它们所返回的值是允许和接受者it或者this对象的类型不同。但是并不是所有的标准函数都是返回作用域的最后一个对象。例如T.also函数。

val original = "abc"
original.let {
 println("The original String is $it") // "abc"
 it.reversed()
}.let {
 println("The reverse String is $it") // "cba"
 it.length
}.let {
 println("The length of the String is $it") // 3
}

original.also {
 println("The original String is $it") // "abc"
 it.reversed()
}.also {
 println("The reverse String is ${it}") // "abc"
 it.length
}.also {
 println("The length of the String is ${it}") // "abc"
}

从上面两段代码可以看出T.let和T.also的返回值使不同的。T.let返回的是作用域中的最后一个对象,它的值和类型都可以改变。但是T.also不管调用多少次返回的都是原来的original对象。

对于T.let和T.also都能够进行链式操作,那么我们现在结合一下T.let和T.also的链式调用来看一下在实际场景中的应用。

//原始函数
fun makeDir(path: String): File {
 val result = File(path)
 result.mkdirs()
 return result
}

//通过let和also的链式调用改进后的函数
fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

扩展函数的特性

到目前为止除了T.apply没有使用到以外,根据上面的用法我们可以总结出来这些标准函数的三大特性。

  • 它们都有自己的作用域
  • 它们作用域中的接收者是this或者it
  • 它们都有一个返回值,返回最后一个对象(this)或者调用者自身(itself)

由此可想到对于T.apply无非也就是这三个特性。对于T.apply它作用域中的接收者是this,并且返回的调用者T。因此,T.apply的其中一个使用场景可以用来创建一个Fragment,代码如下所示:

// 使用普通的方法创建一个Fragment
fun createInstance(args: Bundle) : MyFragment {
 val fragment = MyFragment()
 fragment.arguments = args
 return fragment
}

// 通过apply来改善原有的方法创建一个Fragment
fun createInstance(args: Bundle)
    = MyFragment().apply { arguments = args }

我们也能够通过T.apply的链式调用创建一个Intent:

// 普通创建Intent方法
fun createIntent(intentData: String, intentAction: String): Intent {
 val intent = Intent()
 intent.action = intentAction
 intent.data=Uri.parse(intentData)
 return intent
}

// 通过apply函数的链式调用创建Intent
fun createIntent(intentData: String, intentAction: String) =
  Intent().apply { action = intentAction }
    .apply { data = Uri.parse(intentData) }

如何选择使用

在这里我们通过一个树状图来看一下对着五个标准函数的区别,使用以及如何选取标准函数(图片来源于参考文献当中)

总结

在这里做一下总结,我们可以看出在这五个通用标准函数当中它们的特性也是十分的简单,无非也就是接收者和返回值的不同。对于with,T.run,T.apply接收者是this,而T.let和T.also接受者是it;对于with,T.run,T.let返回值是作用域的最后一个对象(this),而T.apply和T.also返回值是调用者本身(itself)。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

您可能感兴趣的文章:

  • Kotlin中let()with()run()apply()also()函数的使用方法与区别
(0)

相关推荐

  • Kotlin中let()with()run()apply()also()函数的使用方法与区别

    相比Java, Kotlin提供了不少高级语法特性.对于一个Kotlin的初学者来说经常会写出一些不够优雅的代码.在Kotlin中的源码标准库(Standard.kt)中提供了一些Kotlin扩展的内置函数可以优化kotlin的编码.Standard.kt是Kotlin库的一部分,它定义了一些基本函数. 这个源代码文件虽然一共不到50行代码,但是这些函数功能都非常强大. 一.回调函数的Kotin的lambda的简化 在Kotlin中对Java中的一些的接口的回调做了一些优化,可以使用一个lamb

  • Kotlin中标准函数run、with、let、also与apply的使用和区别详解

    前言 和Java相比,在Kotlin中提供了不少新的特性.这次我们就来聊一聊Kotlin的一些通用的扩展标准函数run,with,let,also和apply.对于这五个标准函数它们都存在于Kotlin的源码标准库当中,也就是在Standard.kt文件当中.它们都是适用于任何对象的通用扩展函数.但是对于run,with,let,also和apply这五个函数他们的用法及其相似,以至于我们无法确定去选择使用哪一个.那么现在我们就来聊一下这五个函数它们的使用方法,它们的不同之处以及在什么场景下去使

  • sql中的left join及on、where条件关键字的区别详解

    LEFT JOIN 关键字会从左表 (table_name1) 那里返回所有的行,即使在右表 (table_name2) 中没有匹配的行. LEFT JOIN 关键字语法 SELECT column_name(s) FROM table_name1 LEFT JOIN table_name2 ON table_name1.column_name=table_name2.column_name 注释:在某些数据库中, LEFT JOIN 称为 LEFT OUTER JOIN. 创建两张表并插入一些

  • ubuntu系统中/etc/rc.local和/etc/init.d/rc.local的区别详解

    前言 我们在ubuntu下要把一个程序加入开机启动,一般可以通过修改rc.local来完成,但ubuntu下有两个rc.local文件.分别是/etc/rc.local和/etc/init.d/rc.local.可以看一下两个文件的内容找到他俩的关系: /etc/init.d/rc.local #! /bin/sh ### BEGIN INIT INFO # Provides: rc.local # Required-Start: $all # Required-Stop: # Default-

  • Python中正则表达式match()、search()函数及match()和search()的区别详解

    match()和search()都是python中的正则匹配函数,那这两个函数有何区别呢? match()函数只检测RE是不是在string的开始位置匹配, search()会扫描整个string查找匹配, 也就是说match()只有在0位置匹配成功的话才有返回,如果不是开始位置匹配成功的话,match()就返回none 例如: #! /usr/bin/env python # -*- coding=utf-8 -*- import re text = 'pythontab' m = re.ma

  • Android中dip、dp、sp、pt和px的区别详解

    1.概述过去,程序员通常以像素为单位设计计算机用户界面.例如:图片大小为80×32像素.这样处理的问题在于,如果在一个每英寸点数(dpi)更高的新显示器上运行该程序,则用户界面会显得很小.在有些情况下,用户界面可能会小到难以看清内容.由此我们采用与分辨率无关的度量单位来开发程序就能够解决这个问题.Android应用开发支持不同的度量单位. 2.度量单位含义dip: device independent pixels(设备独立像素). 不同设备有不同的显示效果,这个和设备硬件有关,一般我们为了支持

  • python中break、continue 、exit() 、pass终止循环的区别详解

    python中break.continue .exit() .pass区分 1.break:跳出循环,不再执行 Python break语句,就像在C语言中,打破了最小封闭for或while循环. break语句用来终止循环语句,即循环条件没有False条件或者序列还没被完全递归完,也会停止执行循环语句. break语句用在while和for循环中. 如果您使用嵌套循环,break语句将停止执行最深层的循环,并开始执行下一行代码. 例1: break跳出while死循环 ```python wh

  • 深入SQL Server中定长char(n)与变长varchar(n)的区别详解

    char(n)是定长格式,格式为char(n)的字段固定占用n个字符宽度,如果实际存放的数据长度超过n将被截取多出部分,如果长度小于n就用空字符填充. varchar(n)是变长格式,这种格式的字段根据实际数据长度分配空间,不浪费对于的空间,但是搜索数据的速度会麻烦一点. 一般地说,只要一个表有一个字段定义为varchar(n)类型,那么其余用char(n)定义的字段实际上也是varchar(n)类型. 如果你的长度本身不长,比如就3-10个字符,那么使用char(n)格式效率比较高,搜索速度快

  • java线程中start和run的区别详解

    这篇文章主要介绍了java线程中start和run的区别详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 public class Test1 extends Thread { @Override public void run() { while (true) { System.out.println(Thread.currentThread().getName()); } } public static void main(String[

  • docker中的run/cmd/entrypoint的区别详解

    Dockerfile中run.cmd和entrypoint都能够用于执行命令,下面是三者的主要用途: run命令执行命令并创建新的镜像层,通常用于安装软件包 cmd命令设置容器启动后默认执行的命令及其参数,但CMD设置的命令能够被docker run命令后面的命令行参数替换 entrypoint配置容器启动时的执行命令,不会被忽略,一定会被执行,即使运行 docker run时指定了其他命令. Shell格式和Exec格式运行命令 我们可以用下面两种格式指定run.cmd和entrypoint要

  • java 中同步方法和同步代码块的区别详解

    java 中同步方法和同步代码块的区别详解 在Java语言中,每一个对象有一把锁.线程可以使用synchronized关键字来获取对象上的锁.synchronized关键字可应用在方法级别(粗粒度锁)或者是代码块级别(细粒度锁). 问题的由来: 看到这样一个面试题: //下列两个方法有什么区别 public synchronized void method1(){} public void method2(){ synchronized (obj){} } synchronized用于解决同步问

随机推荐