写在前面
在学习了Kotlin基础学习1,Kotlin基础学习2,Kotlin基础学习3之后,我们对Koltin的基础有了一定的了解。但就这样还是不够的,Kotlin里还有更多的特性等着我们去学习。这阶段可能会出现很多错误,希望看出来的老哥能指点一下。这次学习Kotlin中的标准函数、静态方法、延迟初始化与密封类。
Kotlin中的标准函数
介绍
Kotlin的标准函数指的是Standard.kt文件中定义的函数,任何Kotlin代码都可以自由地调用所有的标准函数。虽然标准函数并不多,但要一次性全部学完自然是不可能的。这里就介绍几个最常用的标准函数。之前我们在Kotlin基础学习3中学习了let函数的用法,主要用于配合?.操作符进行辅助判空,这里就不再多言了。
with函数
with函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda表达式中的最后一行代码作为返回值返回。如下:
val result = with(obj){
// 这里是obj的上下文
"value" // with函数的返回值
}
那么这个函数有什么用呢?它可以让我们在连续调用同一个对象的多个方法时让代码变得精简,比如:
val list = listOf("Apple","Banana","Orange","Pear","Grape")
val result = with(StringBuilder()){
append("Start eating fruits \n")
for(fruit in list){
append(fruit).append("\n")
}
append("ate all fruits")
toString()
}
println(result)
这段代码应该很好理解,首先我们给with函数传入了一个StringBuilder对象,之后Lambda表达式的就是这个StringBuilder对象了,那么我们调用append方法自然就不需要加上前面的实例了。最后,Lambda表达式的最后一行会作为返回值返回,然后赋值给了result,把result输出。
run函数
run函数的用法和使用场景与with函数也差不太多,只是在语法上有一些区别。首先run函数是不能直接调用的,一定要调用某个对象的run函数才行。其次,run函数只接收一个Lambda参数,并在Lambda表达式中提供调用对象的上下文,其他方面和with函数就没什么区别了,如下:
val list = listOf("Apple","Banana","Orange","Pear","Grape")
val result = StringBuilder().run{
append("Start eating fruits \n")
for(fruit in list){
append(fruit).append("\n")
}
append("ate all fruits")
toString()
}
println(result)
总得来说,变化很小,只是换了一下写法而已。
apply函数
apply函数与上面学习的run函数也十分的类似,但差别在于apply函数无法指定返回值,只能自动返回调用对象本身,如下:
val list = listOf("Apple","Banana","Orange","Pear","Grape")
val result = StringBuilder().apply{
append("Start eating fruits \n")
for(fruit in list){
append(fruit).append("\n")
}
append("ate all fruits")
}
println(result.toString())
因为apply函数无法指定返回值,只能返回调用对象本身,所以这里的result就是一个StringBuilder对象,我们在最后输出的时候自然还需要调用他的toString方法才行。
总结
总的来说,这几个标准函数用法和使用场景都十分相似,在实际编程中我们挑选合适的使用就可以了。
静态方法
介绍
静态方法,一直在用java的我们一定不会陌生,在方法上声明一个static关键字就可以了。但非常奇怪的是,Kotlin极度弱化了静态方法这个概念,想在Koltin中定义一个静态方法反而不是那么简单。为什么会这样呢?因为Koltin提供了比静态方法更好的语法特性——单例类。像工具类这种功能,Koltin就推荐我们使用单例类,在使用上也十分方便。但单例类中每个方法都变成类似静态方法的调用方式了,那如果我们有一些方法不想这么搞怎么办呢?我们可以先把单例类变成普通类,然后使用companion object:
class Util{
fun doAction1(){}
companion object{
fun doAction2(){}
}
}
现在,doAction1()和doAction2()方法就完全不一样了,想使用doAction1()就必须先实例化,但doAction2()就不需要。不过doAction2()仍然不是一个静态方法,companion object这个关键字实际上会在类中创建一个伴生类,而doAction2()就是定义在伴生类中的方法,不过Kotlin会保证每一个Util类始终只会存在一个伴生类对象。
总的来说,在开发里,上述的特性已经足够了。可如果我比较固执,就是想用静态方法怎么办?
使用注解
如果想真的定义的一个静态方法,只需要在方法上加上@JvmStatic注解就好了:
class Util{
fun doAction1(){}
companion object{
@JvmStatic
fun doAction2(){}
}
}
但要注意,@JvmStatic注解只能加在单例类或companion object 中的方法上。这次我们的doAction2()是一个真正的静态方法了。
顶层方法
顶层方法指的是那些没有定义在任何类中的方法,比如我们的main()方法。Koltin会将所有的顶层方法全部编译成静态方法,因此只要你定义一个顶层方法,那么他就一定是静态方法。
比如,我们创建一个kotlin文件,起名叫Helper.kt,里面输入我们想要的方法:
fun doSomething(){}
那么要怎么使用这个静态方法呢?十分简单,在项目的任何地方敲就是了。但如果想在java中调用,就必须要加上类名了,比如我们刚才定义的kotlin文件叫Helper.Kt,那么就可以使用HeplerKt.doSomething()来使用。这里的HelperKt是Kotlin编译器自动生成的,基本就是文件名+Kt。
变量延迟初始化
引入
Koltin语言的许多特性我们已经学习了,但有时候很多特性会给我们的编程造成不方便,比如,如果你的类中存在很多全局变量实例,尽管你清楚的知道他们不会为空,但为了保证他们能够满足Kotlin的空指针检查语法标准,你也不得不做许多的非空判断保护才行。这就十分的让人烦了,比如下面的例子:
class MainActivity:AppCompatActivity(),View.OnClickListener{
private var adapter:MsgAdapter? = null
override fun onCreate(savedInstanceState : Bundle?){
...
adapter = MsgAdapter(msgList)
...
}
override fun onClick(v:View?){
...
adapter?.notifyItemInserted(msgList.size - 1)
}
}
这里我们将adapter设置成了全局变量,但他的初始化工作是在onCreate()方法执行的,因此我们不得不把他设置为空,同时声明为MsgAdapter?。虽然我们会确保自己已经初始化了,且能确保onClick()必然会在onCreate之后执行,但我们必须在onClick()方法里对adapter进行判空才行。
当我们代码中的全局变量实例越来越多时,就会显得越来越烦人,因为很多代码都只是为了满足编译器的要求。
有没有什么办法解决呢,十分简单。使用lateinit即可。
使用
延迟初始化使用的是lateinit关键字,它就是在告诉Kotlin编译器,这个变量我们会在之后初始化,就不需要提前赋值为null了。那么我们上面的代码就可以改写:
class MainActivity:AppCompatActivity(),View.OnClickListener{
private lateinit var adapter:MsgAdapter
override fun onCreate(savedInstanceState : Bundle?){
...
adapter = MsgAdapter(msgList)
...
}
override fun onClick(v:View?){
...
adapter.notifyItemInserted(msgList.size - 1)
}
}
当然,使用lateinit方法不是没有任何风险的,如果一个变量还没初始化的时候就使用这个变量,程序自然会崩溃,抛出异常。所以,当我们对全局变量使用lateinit关键字的时候,要先确保已经初始化了。
当然,我们也可以通过代码来判断是否完成了初始化:
if(!::adapter.isInitialized){
adapter = MsgAdapter(msgList)
}
虽然语法看起来很奇怪,但这是固定写法。::adapter.isInitialized可以判断adapter变量是否已经初始化。再取反,就表示如果没有初始化时去立即初始化。
密封类
介绍
一般来讲,密封类会结合RecyclerView里的ViewHolder一起使用。当然,它的使用场景不仅限于此。有了它,可以帮助我们写出更加规范和安全的代码。
使用
下面,我们先来看一个例子:
interface Result
class Success(val msg:String):Result
class Failure(val error:Exception):Result
我们定义了一个Result接口,然后定义了两个类去实现Result接口,Success表示成功时的结果,Failure表示失败时的结果。接下来,再定义一个getResultMsg()方法,用来获取执行结果:
fun getResultMsg(result:Result) = when(result){
is Success -> result.msg
is Failure -> result.error.message
else -> throw IllegalArgumentException()
}
getResultMsg方法接收一个Result参数,我们通过when语句判断是哪种类型,是成功就返回成功的消息,是错误就返回错误的消息。但这里的else条件是毫无意义的,这个else是根本不可能走到的,但为了满足编译器的需求,我们只能加上这个else分支。
不过,要解决这种情况也是十分简单的,使用密封类即可:
sealed class Result
class Success(val msg:String):Result()
class Failure(val error:Exception):Result()
可以看到,差别不是很大,我们只是把interface关键字变成了sealed class。此外,由于密封类是一个可以继承的类,所以要加上括号。那么刚才的when判断里的else分支就不需要了:
fun getResultMsg(result:Result) = when(result){
is Success -> result.msg
is Failure -> result.error.message
}
这是为什么呢?因为当when中传入一个密封类变量作为条件时,编译器会自动检查该密封类有哪些子类,并强制要求你将每一个子类的条件都处理了。这样的话,就可以保证不写else也可以不漏掉任何分支了。
总结
总的来说,掌握了标准函数的用法,且对Koltin中的静态函数进行了一定程度的了解。也对一些由于编译器的要求所出现的不方便的情况有了解决的能力。收获还是蛮多的,希望能在实战中加深理解吧。