2017-06-20 by Liuqingwen | Tags: Kotlin | Hits

我们经常能够在 Java 相关技术博客中看到类似这样的文章: Java 中 X 种单例模式的写法。的确,单例模式是一个简单但又很重要的设计模式,特别是在多线程编程中,它的实现方法各有不同,也是适应各种不同的场合。不过,现在有了 Kotlin ,那都不是事了,忘记那个 X 吧,一个 object 关键字轻松搞定!真的,相信我,生活就是要那么容易。 

在 Kotlin 中,除了 object 关键字还有个 companion object 关键字,这个又是什么鬼?怎么使用?有啥区别?在没有仔细阅读相关文档资料之前还真有点傻傻分不清了。实践出真知,在经过简单的练习加上相关博客文章、源码阅读、谷歌搜索后,我心里所认识的 object 和 companion object 是这样的:
但是,这些认识都是停留在表面上,在我继续阅读《 Kotlin in Action 》这本书相应章节后,我能发现它们的使用场景和功能点远不止这些!究其原因,主要是我并没有完全弄清楚它们的原理以及它们之间的差别,不论是 object 还是 companion object ,它们的共性和区别还有这些:
既然这俩兄弟有这么多异同点,那么我觉得非常有必要总结一下,以便将来能够更加得心应手地使用 Kotlin 吧。
object 可以轻松实现 Kotlin 单例模式, 它可以定义在全局之中,也可以定义在类的内部。但是要注意几点:
object OutObject {
    val outParameter = "out"
    fun outFunction(parameter:String) {
        println("Out object function result: $parameter.")
    }
}
class MyClass {
    val classParameter = "class"
    object InnerObject {
        //val innerParameter = this.classParameter //error: 1,this refers to InnerObject 2,classParameter cannot be reached
        val innerParameter = "inner"
        fun innerFunction(parameter:String) {
            println("Out object function result: $parameter.")
        }
    }
}在 Android 开发中,我们经常会设置一个接口匿名类作为点击事件的参数: setOnClickListener(View.OnClickListener) ,这个时候在 Kotlin 中就可以使用 object 来表达那些匿名内部类。同时 object 相比 Java  更加强大,在用其表达内部类的时候有这几个注意点:
interface MyInterface1
interface MyInterface2
open class MySuperClass(parameter:String)
//button.setOnClickListener( object:OnClickListener { //... } ) //no name specified
class MyClass {
    object AnonymousSubClass:MySuperClass("something"), MyInterface1, MyInterface2{
        //do something...
    }
    val anonymousClass = AnonymousClass
    object AnonymousClass:MyInterface1, MyInterface2 {
        //do something...
    }
    val anotherAnonymous = object:MyInterface1 {
        //type inferred
    }
    val againAnonymous:MyInterface1 = object:MyInterface1, MyInterface2 {
        //type cannot be inferred
    }
}我们知道在 Java 中,内部类是不可以访问外部的非 final 成员变量的,也就是说:它不允许更改变量值!但是, Kotlin 的 object 可以。看代码:
interface MyInterface { fun operateVariable() }
class MyClass {
    fun operationWithInterface(anonymous:MyInterface) { anonymous.operateVariable() }
    
    init {
        var localVariable = 1
        this.operationWithInterface(object : MyInterface, MyInterface1 {
            override fun operateVariable() {
                localVariable += 1
            }
        })
        println("Local variable value: $localVariable") //print: Local variable value: 2
    }
}就是那么霸道!写了那么多 object ,我们再看看 companion object ,可谓是 object 的孪生兄弟,它可以说是为 Java 里的 static 而生的 object 。
和 object 不同, companion object 的定义完全属于类的本身,所以 companion object 肯定是不能脱离类而定义在全局之中。它就像 Java 里的 static 变量,所以我们定义 companion object 变量的时候也一般会使用大写的命名方式。
同时,和 object 类似,可以给 companion object 命名,也可以不给名字,这个时候它会有个默认的名字: Companion ,而且,它只在类里面能定义一次:
class MyClass2 {
    companion object CompanionName {
        val INNER_PARAMETER = "can only be inner"
        fun newInstance() = MyClass2("name")
    }
}
class MyClass3 {
    companion object {
        val INNER_PARAMETER = "can only be inner"
    }
}
fun main(vararg args:String) {
    println(MyClass2.CompanionName.INNER_PARAMETER == MyClass2.INNER_PARAMETER) //print: true
    println(MyClass3.Companion.INNER_PARAMETER == MyClass3.INNER_PARAMETER) //print: true
}和 object 还是一样, companion object 也可以实现接口,因为 companion object 寄生于类,甚至类还可以直接作为实现了相应得接口的参数形式传入,拗口,看代码:
interface MyInterface { fun operateVariable() }
fun operateClass(interfaceObject:MyInterface) = interfaceObject.operateVariable()
class MyClass3 {
    companion object:MyInterface {
        override fun operateVariable() {
            //do something...
        }
    }
}
fun main(vararg args:String) {
    operateClass(MyClass3) //MyClass3 is now as the instance of MyInterface
}Kotlin 的扩展功能非常强大,是程序猿爱不释口且口口相传的实用特性之一。那么我们怎么扩展类的静态成员呢?这个时候当然是 companion object 派上用场的时刻了!
class MyClass2 {
    companion object {
        val INNER_PARAMETER = "can only be inner"
    }
}
fun main(vararg args:String) {
    fun MyClass2.Companion.operateVariable() {
        println(this.INNER_PARAMETER)
    }
    
    MyClass2.operateVariable() //print: can only be inner
}怎么样? Kolint 就是那么强大!不得不服!

以上就是我自己总结的一些基本点,总之, Kolint 真不愧是一个门好语言啊!另外官方并不建议我们到处滥用 object关键字,因为它不易控制也不利于测试,毕竟定义即实例化嘛,当然除了很容易实现单例模式,工厂模式也很简单,不信你可以试试。 

话又说回来,我建议大家有时间还是有必要再把 Kotlin 代码转换成 Java 源码再分析一遍,这个时候 @JvmStatic 和 @JvmField 标志就发挥作用了。我写这篇文章的时候我并没有下功夫继续深究,有机会还会再去看看转化 Java 部分源码,那样会更加加深对 object 和 companion object 甚至整个 Kotlin 语言的认识吧!好吧,我就菜鸟一枚,那接下来就交给你总结一下并发表给我学习学习吧!谢谢! 

最后,引用官方文档说明,比较它们的实例化过程:
资料: Kotlin笔记 Object表达式和声明: http://www.jianshu.com/p/f316ff2f4306 Object Expressions and Declarations: https://kotlinlang.org/docs/reference/object-declarations.html