90%的开发者都不知道的 Kotlin技巧以及原理解析(一)

这段代码的意思就是,通过 plus(+) 操作符合并两个 map,返回一个新的 map, 但是忽略了默认值,所以看到上面的错误信息,我们在开发的时候需要注意这点。

使用 require 或者 check 函数作为条件检查


// 传统的做法

val age = -1;

if (age <= 0) {

throw IllegalArgumentException(“age must not be negative”)

}

// 使用 require 去检查

require(age > 0) { “age must be negative” }

// 使用 checkNotNull 检查

val name: String? = null

checkNotNull(name){

“name must not be null”

}

那么我们如何在项目中使用呢,具体的用法可以查看我 GitHub 上的项目 DataBindingDialog.kt 当中的用法。

如何区分和使用 run, with, let, also, apply


感谢大神 Elye 的这篇文章提供的思路 Mastering Kotlin standard functions

run, with, let, also, apply 都是作用域函数,这些作用域函数如何使用,以及如何区分呢,我们将从以下三个方面来区分它们。

  • 是否是扩展函数。

  • 作用域函数的参数(this、it)。

  • 作用域函数的返回值(调用本身、其他类型即最后一行)。

是否是扩展函数

首先我们来看一下 with 和 T.run,这两个函数非常的相似,他们的区别在于 with 是个普通函数,T.run 是个扩展函数,来看一下下面的例子。

val name: String? = null

with(name){

val subName = name!!.substring(1,2)

}

// 使用之前可以检查它的可空性

name?.run { val subName = name.substring(1,2) }?:throw IllegalArgumentException(“name must not be null”)

在这个例子当中,name?.run 会更好一些,因为在使用之前可以检查它的可空性。

作用域函数的参数(this、it)

我们在来看一下 T.run 和 T.let,它们都是扩展函数,但是他们的参数不一样 T.run 的参数是 this, T.let 的参数是 it。

val name: String? = “hi-dhl.com”

// 参数是 this,可以省略不写

name?.run {

println(“The length is ${this.length} this 是可以省略的 ${length}”)

}

// 参数 it

name?.let {

println(“The length is ${it.length}”)

}

// 自定义参数名字

name?.let { str ->

println(“The length is ${str.length}”)

}

在上面的例子中看似 T.run 会更好,因为 this 可以省略,调用更加的简洁,但是 T.let 允许我们自定义参数名字,使可读性更强,如果倾向可读性可以选择 T.let。

作用域函数的返回值(调用本身、其他类型)

接下里我们来看一下 T.let 和 T.also 它们接受的参数都是 it, 但是它们的返回值是不同的 T.let 返回最后一行,T.also 返回调用本身。

var name = “hi-dhl”

// 返回调用本身

name = name.also {

val result = 1 * 1

“juejin”

}

println(“name = ${name}”) // name = hi-dhl

// 返回的最后一行

name = name.let {

val result = 1 * 1

“hi-dhl.com”

}

println(“name = ${name}”) // name = hi-dhl.com

从上面的例子来看 T.also 似乎没有什么意义,细想一下其实是非常有意义的,在使用之前可以进行自我操作,结合其他的函数,功能会更强大。

fun makeDir(path: String) = path.let{ File(it) }.also{ it.mkdirs() }

当然 T.also 还可以做其他事情,比如利用 T.also 在使用之前可以进行自我操作特点,可以实现一行代码交换两个变量,在后面会有详细介绍

T.apply 函数

通过上面三个方面,大致了解函数的行为,接下来看一下 T.apply 函数,T.apply 函数是一个扩展函数,返回值是它本身,并且接受的参数是 this。

// 普通方法

fun createInstance(args: Bundle) : MyFragment {

val fragment = MyFragment()

fragment.arguments = args

return fragment

}

// 改进方法

fun createInstance(args: Bundle)

= MyFragment().apply { arguments = args }

// 普通方法

fun createIntent(intentData: String, intentAction: String): Intent {

val intent = Intent()

intent.action = intentAction

intent.data=Uri.parse(intentData)

return intent

}

// 改进方法,链式调用

fun createIntent(intentData: String, intentAction: String) =

Intent().apply { action = intentAction }

.apply { data = Uri.parse(intentData) }

汇总

以表格的形式汇总,更方便去理解

| 函数 | 是否是扩展函数 | 函数参数(this、it) | 返回值(调用本身、最后一行) |

| — | — | — | — |

| with | 不是 | this | 最后一行 |

| T.run | 是 | this | 最后一行 |

| T.let | 是 | it | 最后一行 |

| T.also | 是 | it | 调用本身 |

| T.apply | 是 | this | 调用本身 |

使用 T.also 函数交换两个变量

接下来演示的是使用 T.also 函数,实现一行代码交换两个变量?我们先来回顾一下 Java 的做法。

int a = 1;

int b = 2;

// Java - 中间变量

int temp = a;

a = b;

b = temp;

System.out.println("a = “+a +” b = "+b); // a = 2 b = 1

// Java - 加减运算

a = a + b;

b = a - b;

a = a - b;

System.out.println("a = " + a + " b = " + b); // a = 2 b = 1

// Java - 位运算

a = a ^ b;

b = a ^ b;

a = a ^ b;

System.out.println("a = " + a + " b = " + b); // a = 2 b = 1

// Kotlin

a = b.also { b = a }

println(“a = ${a} b = ${b}”) // a = 2 b = 1

来一起分析 T.also 是如何做到的,其实这里用到了 T.also 函数的两个特点。

  • 调用 T.also 函数返回的是调用者本身。

  • 在使用之前可以进行自我操作。

也就是说 b.also { b = a } 会先将 a 的值 (1) 赋值给 b,此时 b 的值为 1,然后将 b 原始的值(2)赋值给 a,此时 a 的值为 2,实现交换两个变量的目的。

in 和 when 关键字


使用 in 和 when 关键字结合正则表达式,验证用户的输入,这是一个很酷的技巧。

// 使用扩展函数重写 contains 操作符

operator fun Regex.contains(text: CharSequence) : Boolean {

return this.containsMatchIn(text)

}

// 结合着 in 和 when 一起使用

when (input) {

in Regex(“[0–9]”) -> println(“contains a number”)

in Regex(“[a-zA-Z]”) -> println(“contains a letter”)

}

in 关键字其实是 contains 操作符的简写,它不是一个接口,也不是一个类型,仅仅是一个操作符,也就是说任意一个类只要重写了 contains 操作符,都可以使用 in 关键字,如果我们想要在自定义类型中检查一个值是否在列表中,只需要重写 contains() 方法即可,Collections 集合也重写了 contains 操作符。

val input = “kotlin”

when (input) {

in listOf(“java”, “kotlin”) -> println(“found ${input}”)

in setOf(“python”, “c++”) -> println(“found ${input}”)

else -> println(" not found ${input}")

}

Kotlin 的单例三种写法


我汇总了一下目前 Kotlin 单例总共有三种写法:

  • 使用 Object 实现单例。

  • 使用 by lazy 实现单例。

  • 可接受参数的单例(来自大神 Christophe Beyls)。

使用 Object 实现单例

代码:

object WorkSingleton

Kotlin 当中 Object 关键字就是一个单例,比 Java 的一坨代码看起来舒服了很多,来看一下编译后的 Java 文件。

public final class WorkSingleton {

public static final WorkSingleton INSTANCE;

static {

WorkSingleton var0 = new WorkSingleton();

INSTANCE = var0;

}

}

通过 static 代码块实现的单例,优点:饿汉式且是线程安全的,缺点:类加载时就初始化,浪费内存。

使用 by lazy 实现单例

利用伴生对象 和 by lazy 也可以实现单例,代码如下所示。

class WorkSingleton private constructor() {

companion object {

// 方式一

val INSTANCE1 by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) { WorkSingleton() }

// 方式二 默认就是 LazyThreadSafetyMode.SYNCHRONIZED,可以省略不写,如下所示

val INSTANCE2 by lazy { WorkSingleton() }

}

}

lazy 的延迟模式有三种:

  • 上面代码所示 mode = LazyThreadSafetyMode.SYNCHRONIZED,lazy 默认的模式,可以省掉,这个模式的意思是:如果有多个线程访问,只有一条线程可以去初始化 lazy 对象。

  • 当 mode = LazyThreadSafetyMode.PUBLICATION 表达的意思是:对于还没有被初始化的 lazy 对象,可以被不同的线程调用,如果 lazy 对象初始化完成,其他的线程使用的是初始化完成的值。

  • mode = LazyThreadSafetyMode.NONE 表达的意思是:只能在单线程下使用,不能在多线程下使用,不会有锁的限制,也就是说它不会有任何线程安全的保证以及相关的开销。

通过上面三种模式,这就可以理解为什么 by lazy 声明的变量只能用 val,因为初始化完成之后它的值是不会变的。

可接受参数的单例

但是有的时候,希望在单例实例化的时候传递参数,例如:

Singleton.getInstance(context).doSome()

上面这两种形式都不能满足,来看看大神 Christophe Beyls 在这篇文章给出的方法 Kotlin singletons with argument 代码如下。

class WorkSingleton private constructor(context: Context) {

init {

// Init using context argument

}

companion object : SingletonHolder<WorkSingleton, Context>(::WorkSingleton)

}

open class SingletonHolder<out T : Any, in A>(creator: (A) -> T) {

private var creator: ((A) -> T)? = creator

@Volatile

private var instance: T? = null

fun getInstance(arg: A): T {

val i = instance

if (i != null) {

return i

}

return synchronized(this) {

val i2 = instance

if (i2 != null) {

i2

} else {

val created = creator!!(arg)

instance = created

creator = null

created

}

}

}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Python工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)

因此收集整理了一份《2024年Python开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-ZTXpHW2n-1712461458732)]

[外链图片转存中…(img-0OtGW89H-1712461458732)]

img

img

img

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上前端开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以扫码获取!!!(备注Python)

img
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值