1,这两天在用kotlin写android项目,当写工具类的时候,发现把以前的工具类转成kotlin以后,都变成object修饰的类了。要知道object是表示单例,正常情况我们写工具类只需要静态方法而不需要单例的,所以这里有点困惑。
2,后来发现有companion object(伴生对象),最开始我以为这个伴生对象有点类似于java中的静态代码块,这样就可以不用object单例来写工具类了,用它就可以了,后来发现是我理解错了,所以就写一篇文章加深下理解吧。
之前也写过一篇文章讲了一下Kotlin单例模式,可以跟这篇做一个对比。
一,object 关键字
在《kotlin-reference-chinese》给到的官方文档上我们可以看到,在object关键字中分了两块讲解,对象表达式和对象声明。
对象表达式
创建一个继承自某个(或某些)类型的匿名类的对象
interface CallBackListener {
fun requestSuccess()
fun requestFail()
}
class AddCallBackListener{
fun addListener(listener: CallBackListener){}
}
val add = AddCallBackListener() //创建对象
add.addListener(object : CallBackListener{ //传递listener参数进去
override fun requestSuccess() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun requestFail() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
})
从上面代码我们可以看到。我们写了一个接口类,然后写了一个AddCallBackListener类,并在类里面有一个方法,需要传递一个listener进去,下面代码部分时我们创建的一个对象并调用对象中的方法并传递listener参数进去。
我们知道在java中我们一般传递listener参数的时候都是new Listener()这里看object有点new对象的意思。
如果超类型有一个构造函数,则必须传递适当的构造函数参数给它,多个超类型可以跟在冒号后面的逗号分隔的列表指定
open class A(x: Int) {
public open val y: Int = x
}
interface B {}
val ab: A = object : A(1),B{
override val y: Int
get() = 15
}
这段代码我们可以看到我们其实是声明了一个对象ab,这个对象是A对象并且实现了B中的接口。
如果我们只需要“一个对象而已”并不需要特殊的超类型
fun TestObject(){
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
println(adHoc.x + adHoc.y)
}
这么写就可以了。其实是一个没有名字的对象而已(匿名对象)。
匿名对象可以用作本地和私有域中声明的类型,如果你使用匿名对象作为共有函数的返回类型或者共有属性类型,那么该函数或者属性的实际类型会是匿名对象的超类,如果没有任何超类型,就会是Any
class TestOkc{
private fun okc() = object {
val x: String = "x"
}
fun okcT() = object {
val x: String = "x"
}
fun bar(){
val x1 = okc().x
val x2 = okcT().x //这里报错
}
}
图片中我们能看到私有和共有两个对象的返回类型。
和java匿名内部类一样,对象表达式中的代码可以访问来自包含它的作用域的变量,(不同的是,不仅限于final变量)
fun countClicks(add: AddCallBackListener){
var clickCount = 0
add.addListener(object : CallBackListener{
override fun requestSuccess() {
clickCount++
}
override fun requestFail() {
clickCount--
}
})
}
对象声明
单例模式
object Singleton2 {
fun getTestString2(): String {
return "Singleton2"
}
}
println(Singleton2)
println(Singleton2)
println(Singleton2.getTestString2())
输出结果:
com.xp.kotlin.part1.Singleton2@4d405ef7
com.xp.kotlin.part1.Singleton2@4d405ef7
Singleton2
能看到是同一个对象。在object关键字后面跟一个名称,就像声明一个变量一样,这样就是一个单例了,太简单了。调用方法直接类名+方法名即可。
我还发现了一个问题就是在.java文件中调用kotlin的单例直接类名+方法名不行,需要类名+INSTANCE+方法名
object声明的单例可以有超类
interface CallBackListener {
fun requestSuccess()
fun requestFail()
}
object DefaultListener : CallBackListener {
override fun requestSuccess() {
println("requestSuccess")
}
override fun requestFail() {
println("requestFail")
}
}
可以看出kotlin中的单例竟然可以有超类,这个弥补了java中单例的一个不足吧。
二,companion(伴生对象)
类内部的对象可以用companion关键字标记,该伴生对象可通过类名作为限定符来调用。
class Singleton3 {
companion object Factory {
fun getTestString3(): String {
return "Singleton3"
}
}
}
println(Singleton3)
println(Singleton3)
println(Singleton3.getTestString3())
输出结果:
com.xp.kotlin.part1.Singleton3$Factory@6193b845
com.xp.kotlin.part1.Singleton3$Factory@6193b845
Singleton3
从结果我们能看出来,跟单例很像,区别就是输出打印的对象上,单例是直接打印出对象,伴生对象如果有名字就会打印出伴生对象的名字,而且伴生对象的名字可以省略,如果没有名字打印出的伴生对象名字默认是Companion。
我们还发现,如果这个类里面有伴生对象,就可以直接打印这个类(通过类名),而不是需要创建这个类(通过默认构造函数)。而且从打印结果我们也能看出,伴生对象是在类加载的时候已经初始化了。
最开始我以为跟java中静态代码块那样,没有创建对象,后来发现其实是创建对象了。
而且在官方给出的文档上也明确标注了,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员
伴生对象可以实现接口
interface CallBackListener {
fun requestSuccess()
fun requestFail()
}
class Singleton3 {
companion object Factory : CallBackListener{
override fun requestSuccess() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun requestFail() {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
fun getTestString3(): String {
return "Singleton3"
}
}
}
最后,文档也给出了,在JVM平台,如果使用@JvmStatic 注解,你也可以将伴生对象的成员生成为真正的静态方法和字段。
三,对象表达式和对象声明之间的语义差异
- 对象表达式是在使用他们的地方立即执行(及初始化)的
- 对象声明是在第一次被访问到时延迟初始化的
- 伴生对象的初始化是在对应的类被加载(解析)时,与java静态初始化器语义匹配
最后我还有一个疑问就是,为什么我们将Java中的工具类(里面只有静态方法),通过android studio直接转成kotlin的时候他会给我转成一个用object声明的类,这明显就是一个单例模式的类,这个跟java中的静态方法是有区别的吧?欢迎大家交流讨论。