你们好,很久不见。从Kotlin发布到如今已经有快十个年头了,从2016年发布正式版发展到如今已经有愈来愈多的开发者开始使用Kotlin开发项目,特别是安卓开发者,由于谷歌在2017年的 I/O 大会上正式宣布Kotlin正式成为安卓的一级开发语言,在2019年的 I/O大会上又宣布Kotlin为安卓的第一开发语言。。java
我从2018年下半年开始学习的Kotlin,最开始买了一本《Kotlin从零到精通》,跟着学习了一遍,把Kotlin的基本语法学习了一遍,因为待的公司比较小,安卓开发者并很少,并且当时正好有一个新的项目,因此我提议整个项目采用Kotlin进行开发,其实当时很冒险,由于那个项目挺着急,三个安卓开发都不咋会Kotin,因此在项目开发期间一直是边学边写,项目完成以后感受本身对Kotlin已经理解的差很少了(其实只是会用而已,项目中有的地方仍是使用Java)。安全
后来看一些开发者论坛看你们学习Kotlin的愈来愈多,提到的好多东西居然都看不懂,最过度的是已经使用kotlin写了一个项目了,Kotlin中的一些关键字都不知道是干啥用的,实在惭愧。app
以前一直关注郭神的博客,并且安卓入门也看的是郭神的《第二行代码》。后来据说要出《第三行代码》,并且里面有Kotlin的详细讲解,因此书第一天发售的时候就立马抢了一本签名版,还没来的及好好看看,公司就让我出差了,一直到如今才有空看看,看了看感受以前写的代码好多都不太好,好多Kotlin中好用的东西都没使用到,我使用的Kotlin只是把Java代码转成了Kotlin,没有一点意义。好了,下面开始知识点了,敲黑板划重点!ide
Kotlin中的标准函数指的是在 Standard.kt 中定义的函数,下面来写一下我认为常用的标准函数吧!函数
最开始决定使用Kotlin的时候的一个重要缘由就是它把空指针异常提到了语言层面,可是这也是让不少像我同样的开发者头疼的地方。。工具
是,Kotlin为了空安全不容许定义为空的,想要定义的话就必须加上问号,可是。。。。不少状况就像下面的代码:性能
为了Kotlin的空安全必须使用问号点或者两个感叹号点来消除空安全的报错,只是为了消除报错,在Java中根本不须要的好嘛!要是只使用一次两次还好,我们使用问号或者感叹号还好,但若是是一堆调用的呢?好比下面:学习
参数多的时候怎么搞。。不说写的速度,烦都烦死了。。。。ui
后来。。。。发现根本不必这样写啊!能够这样啊:this
var zhu:ZhuJ? = null
fun test() {
zhu?.let { zhu->
zhu.name
zhu.phone
zhu.age
zhu.sex
}
}
复制代码
是否是瞬间感受代码优雅了好多。。。全局变量都没问题,方法的实参更没问题了。。
知识点啊兄弟们,早知道这样就不写一堆问号和感叹号了。。。
这个标准函数的做用是Lambda中的代码会持有对象的上下文,其最后一行代码为返回值。
这么说不太好理解,来一段代码你们先看下吧:
operator fun String.times(n: Int): String {
val sb = StringBuilder()
repeat(n){
sb.append(this)
}
return sb.toString()
}
复制代码
这是一个String类的运算符重载的一个方法,意思很简单,就是重复字符串,参数为重复几回。你们能够发现,在这个方法中的 StringBuilder 对象被使用了好几次,没一会都须要写一次,可是。。。若是使用了with标准函数的话。。。。
operator fun String.times(n: Int): String {
return with(StringBuilder()) {
repeat(n) {
append(this@times)
}
toString()
}
}
复制代码
是否是感受清爽了些许,不少状况能够这样来调用。
这个标准函数的做用其实和 with 基本一致,只是使用方法上有所不一样,with 须要括号中写入对象来进行操做,run 则是对象点进行操做,上面代码使用 run 改写以后的代码以下:
operator fun String.times(n: Int): String {
return StringBuilder().run {
repeat(n) {
append(this@times)
}
toString()
}
}
复制代码
这块要注意了,apply 使用方式和run一致,可是不一样的是:最后一行不做为返回值,废话很少说,还拿上面代码改写:
operator fun String.times(n: Int): String {
return StringBuilder().apply {
repeat(n) {
append(this@times)
}
}.toString()
}
复制代码
with 和 run 的最后一行都是返回值,而apply泽不是,这块必定要注意。
这块须要好好的总结下了,真的是,都写了一个项目了连使用语言的关键字都没认全。。一个一个来!
这个关键字其实使用的不少,在定义全局变量为空的时候并非非得用问号设置为可空的,若是你能够肯定必定不为空可使用 lateinit 这个关键字来定义全局变量,举个栗子:
lateinit var zhuJ: ZhuJ
复制代码
当这样定义全局变量的时候就无需设置为可空了,好比安卓项目中的 adapter ,我们确定能确认会赋值,不会为空,那么就可使用 lateinit 了。
这块须要注意的是,即便我们以为不会为空,但确定会有特殊状况须要进行判断,须要进行判断的话要使用 isInitialized ,使用方法以下:
if (::zhuJ.isInitialized){
// 判断是否已经进行赋值
}
复制代码
这个关键字以前一直没有进行使用,它用来修饰类,含义为密封类,以前一直没搞懂这个密封类有啥说啥用,这两天好好看了下,我理解的做用就是:可使代码更加严密。
这样说感受有点抽象,再举个栗子吧,平时我们在封装一些工具的时候通常只会有成功和失败,我们的作法通常是定义一个接口,而后再定义一个成功类和失败类来实现这个接口,最后再进行判断:
class Success(val msg: String) : Result
class Fail(val error: Throwable) : Result
fun getResult(result: Result) = when (result) {
is Success -> result.msg
is Fail -> result.error.message
else -> throw IllegalArgumentException()
}
复制代码
上面代码都是我们通常写的,虽然只有两种状况,可是必须再写 else 来进行判断,若是不写的话编译就过不了。但若是使用密封类的话就不会有这种状况出现:
sealed class Results
class Success(val mag: String) : Results()
class Failure(val error: Exception) : Results()
fun getMessage(result: Results) {
when (result) {
is Success -> {
println(result.mag)
}
is Failure -> {
println(result.error.toString())
}
}
}
复制代码
不只不用再写else,并且在进行 when 判断时,kotlin 会检查条件是否包含了全部的子类,若是没有会提示你加上,这样就大大提升的代码的鲁棒性,也不会出现没有判断到的问题。
这个关键字是运算符重载,其实在上面标准函数中已经使用到了,就是能够对运算符进行从新自定义,用来实现一些代码上不对劲但实际上对劲的需求,使用起来也很舒服。
这里来讲一下我们经常使用的运算符须要重载的函数吧:加号对应 plus、减号对应minus、乘号对应 times、除号对应 div、取余对应 rem、自增对应 inc、自减对应 dec。
具体使用方法就是上面那样,再写下吧:
operator fun String.times(n: Int): String {}
复制代码
这个关键字能够用来修饰类和方法,它的做用很简单,就是限制不一样 module 的访问,若是在 A module 中定义了一个 internal 方法,那么这个方法只能在 A module 中进行调用,在 B module 中是没法访问的。
这个关键字很简单,用来修饰类,可是。。。只能用来修饰内部类。
我们来写个栗子你们就知道了:
class Test {
var num: String? = null
class Zhu() {
var nums: String? = null
fun adds() {
nums?.let { it.length }
}
}
inner class Jiang() {
var nums: String? = null
fun adds() {
nums?.let {
it.length
}
}
}
}
复制代码
上面代码很简单,只是定义了一个 Test 类,其中一个是直接在内部建立的 Zhu 类,另外一个是使用 inner 关键字修饰的 Jiang 类。我们直接来调用下看下有什么区别吧:
Test().Jiang().nums
Test.Zhu().nums
复制代码
你们发现没有,若是要使用 inner 修饰内部类的话须要先获取到 Test 类的实例才能够进行使用,而直接建立的 Zhu 类则不须要。
这个关键字的意思是内联函数,它的用法很是简单,只须要在高阶函数前加上 inline 关键字便可。若是对高阶函数不太清楚的,建议去看下扔物线的一个视频,好像是讲解 Lanbda 的。
简单说下吧,高阶函数并无想象中的难,只是名字听着感受很高大上而已,简单来讲就是传入方法(其实本质上仍是对象)看成方法参数即为高阶函数。高阶函数的原理其实就是把方法参数转为接口,并建立匿名内部类进行调用,因此每次调用这样的 Lambda 都会建立一个新的匿名内部类和接口实例,形成额外的开销。
因此这就是 inline 出现的缘由,它能够去掉这些开销,并无什么特殊的,只是进行替换,就是在你调用的地方把方法参数进行替换,从而减小内存和性能的开销。来看下使用方法吧:
inline fun high(block:(Int,Int) -> Int,block2:(Int,Int) -> Int){
block.invoke(5,6)
block2.invoke(4,5)
}
复制代码
诶,这个关键字和上面内联函数的关键字好像是吧!这是由于若是一个高阶函数中有两个或以上的方法参数存在的话,若是使用 inline 关键字的话会把全部的方法参数都变为内联函数,为何不都替换呢?由于内联函数的函数类型参数在编译的时候会进行代码替换,因此没有真正的参数类型,但非内联函数的函数类型参数能够自由的传递给其余任何函数,而内联函数类型参数只容许传递给另外一个内联函数。
说了这么多就是为了引出 noinline 的存在乎义,使用方法很简单:
inline fun high(block:(Int,Int) -> Int,noinline block2:(Int,Int) -> Int){
block.invoke(5,6)
block2.invoke(4,5)
}
复制代码
是否是很简单,只要在方法参数前加上 noinline 关键字便可。
既然已经说了 noinline 关键字,那么也得说下 crossnoinline 了。它的做用是:让没法使用内联函数的方法使用内联函数。
为何有没法使用内联函数的函数呢?非内联函数没法直接 return ,可是内联函数能够,因此若是在高阶函数中建立或者使用了另外的 Lambda 或匿名类的实现的话即会报错。再举个栗子:
上面代码使用了我们很是熟悉的 Runnable ,可是发现报错了,为何呢?上面已经说出了答案,这就是 crossinline 关键字的做用,可让没法使用内联函数的函数来使用内联函数:
完美解决!crossinline 的做用主要是用于保证内联函数中的 Lambda 表达式中必定不会使用 return 关键字,这样也就没有冲突了。这样也有一个坏处,就是咱们也没法调用 Runnable 中使用 return 进行返回了。
这个关键字其实很好用,我们可使用它来一些很骚的操做:
val result = "zhujiang" * 3
val a = result begin "zhu"
复制代码
是否是没见过这样的写法?复制到你电脑上确定报错。infix 主要的做用就是定义一些语义上很舒服的写法,好比上面的 result begin "zhu" 这样的调用方式:
infix fun String.begin(prefix:String):Boolean = startsWith(prefix)
复制代码
是否是很好用,是否是已经想到不少骚操做了?哈哈哈
可是!
要注意如下两点:
- infix 不能定义成顶层函数,必须是某个类的成员函数,可以使用扩展方法的方式将它定义到某个类中
- infix 函数必须且只有能接收一个参数,类型的话没有限制。
这个关键字的意思是委托。来一个使用方法看看吧:
class MySet<T>(val help:HashSet<T>) :Set<T> by help{
override fun isEmpty(): Boolean {
return false
}
}
复制代码
能够为一些类建立委托类并重写或添加一些本身写的方法。
泛型你们再熟悉不过了,Java 中我们使用的也很是多,例如 List 、HashMap<String,String>等等。
其实使用和 Java 中差很少,栗子又来了:
class Generic <T>{
fun method(parem:T):T{
return parem
}
}
复制代码
上面是在类上的使用,固然方法中也能够进行使用:
fun <S> meth(parem:S):S{
return parem
}
复制代码
在 Java 中是绝对没有的,也是不现实的,由于 Java 的泛型擦出机制。。。
可是在Kotlin中是能够实现的,可是。。。。有条件!
那么,实化有什么做用呢?来看代码吧:
inline fun <reified T> startActivity(context:Context) {
context.startActivity(Intent(context,T::class.java))
}
复制代码
知道了吧。。很方便的!
这块。。。提及来有点麻烦,下一篇文章来专门写写泛型的逆变和协变吧!先欠着!
先总结到这里吧,其实 Kotlin 中还有不少好玩的东西须要咱们去探索,好比协程,项目中其实用到了不少,但总感受使用的不够好,须要有空好好扣一扣。虽说内容并很少,但也写了很久,文中的知识都是跟着郭神的《第三行代码》学习的,也推荐你们购买,一本书能学到一个知识点就不亏,支持正版,别为了省几十块钱下载盗版。。。
若是本文对你有帮助,请别忘记三连啊。若是本文有描述不恰当或错误的地方,请提出来,感激涕零。就这样,下回见。