Kotlin之 object(对象表达式,对象声明) 和 companion(伴生对象)

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中的静态方法是有区别的吧?欢迎大家交流讨论。

kotlin之Android项目实战

猜你喜欢

转载自blog.csdn.net/wangxp423/article/details/80902846