【学习笔记】区别Kotlin中的object和companion object关键字
一、前言
我们经常能够在 Java 相关技术博客中看到类似这样的文章: Java 中 X 种单例模式的写法。的确,单例模式是一个简单但又很重要的设计模式,特别是在多线程编程中,它的实现方法各有不同,也是适应各种不同的场合。不过,现在有了 Kotlin ,那都不是事了,忘记那个 X 吧,一个 object
关键字轻松搞定!真的,相信我,生活就是要那么容易。
在 Kotlin 中,除了 object
关键字还有个 companion object
关键字,这个又是什么鬼?怎么使用?有啥区别?在没有仔细阅读相关文档资料之前还真有点傻傻分不清了。实践出真知,在经过简单的练习加上相关博客文章、源码阅读、谷歌搜索后,我心里所认识的 object
和 companion object
是这样的:
- object 可以定义在全局也可以在类的内部使用
- object 就是单例模式的化身
- object 可以实现 Java 中的匿名类
- companion object 就是 Java 中的 static 变量
- companion object 只能定义在对应的类中
但是,这些认识都是停留在表面上,在我继续阅读《 Kotlin in Action 》这本书相应章节后,我能发现它们的使用场景和功能点远不止这些!究其原因,主要是我并没有完全弄清楚它们的原理以及它们之间的差别,不论是 object
还是 companion object
,它们的共性和区别还有这些:
- object 可以作为变量的定义也可以是表达式
- object 匿名类可以继承并超越 Java 中匿名类而实现多个接口
- object 表达式当场实例化,但定义的 object 变量是延迟实例化的
- object 和 companion object 都可以为其取名也可以隐姓埋名
- object 匿名内部类甚至可以引用并更改局部变量
- companion object 甚至还可以被扩展
- Java 中需要结合 @JvmStatic 和 @JvmField 使用
- …… 还有很多异同点等着你的开发
既然这俩兄弟有这么多异同点,那么我觉得非常有必要总结一下,以便将来能够更加得心应手地使用 Kotlin 吧。
二、正文
1. object基本定义
object
可以轻松实现 Kotlin 单例模式, 它可以定义在全局之中,也可以定义在类的内部。但是要注意几点:
- object 定义后即刻实例化
- 因此 object 不能有定义构造函数
- 定义在类内部的 object 并不能访问类的成员
1 | object OutObject { |
2. object作为表达式
在 Android 开发中,我们经常会设置一个接口匿名类作为点击事件的参数: setOnClickListener(View.OnClickListener)
,这个时候在 Kotlin 中就可以使用 object
来表达那些匿名内部类。同时 object
相比 Java 更加强大,在用其表达内部类的时候有这几个注意点:
- object 继承父类必须立刻传递父类构造参数
- object 匿名类可以同时实现多个接口
- object 匿名类作为参数并没有名字定义,但是可以为其定义一个变量名,如果实现多接口不能直接用类型推断,拗口吧,请看下面代码:
1 | interface MyInterface1 |
3. object可以访问非final局部变量
我们知道在 Java 中,内部类是不可以访问外部的非 final
成员变量的,也就是说:它不允许更改变量值!但是, Kotlin 的 object
可以。看代码:
1 | interface MyInterface { fun operateVariable() } |
就是那么霸道!写了那么多 object
,我们再看看 companion object
,可谓是 object
的孪生兄弟,它可以说是为 Java 里的 static
而生的 object
。
4. companion object使用方法
和 object
不同, companion object
的定义完全属于类的本身,所以 companion object
肯定是不能脱离类而定义在全局之中。它就像 Java 里的 static
变量,所以我们定义 companion object
变量的时候也一般会使用大写的命名方式。
同时,和 object
类似,可以给 companion object
命名,也可以不给名字,这个时候它会有个默认的名字: Companion
,而且,它只在类里面能定义一次:
1 | class MyClass2 { |
5. 类名可作为接口参数传入
和 object
还是一样, companion object
也可以实现接口,因为 companion object
寄生于类,甚至类还可以直接作为实现了相应得接口的参数形式传入,拗口,看代码:
1 | interface MyInterface { fun operateVariable() } |
6. 扩展类的静态成员
Kotlin 的扩展功能非常强大,是程序猿爱不释口且口口相传的实用特性之一。那么我们怎么扩展类的静态成员呢?这个时候当然是 companion object
派上用场的时刻了!
1 | class MyClass2 { |
怎么样? Kolint 就是那么强大!不得不服!
三、总结
以上就是我自己总结的一些基本点,总之, Kolint 真不愧是一个门好语言啊!另外官方并不建议我们到处滥用 object
关键字,因为它不易控制也不利于测试,毕竟定义即实例化嘛,当然除了很容易实现单例模式,工厂模式也很简单,不信你可以试试。
话又说回来,我建议大家有时间还是有必要再把 Kotlin 代码转换成 Java 源码再分析一遍,这个时候 @JvmStatic
和 @JvmField
标志就发挥作用了。我写这篇文章的时候我并没有下功夫继续深究,有机会还会再去看看转化 Java 部分源码,那样会更加加深对 object
和 companion object
甚至整个 Kotlin 语言的认识吧!好吧,我就菜鸟一枚,那接下来就交给你总结一下并发表给我学习学习吧!谢谢!
最后,引用官方文档说明,比较它们的实例化过程:
- object expressions are executed (and initialized) immediately, where they are used
- object declarations are initialized lazily, when accessed for the first time
- a companion object is initialized when the corresponding class is loaded (resolved), matching the semantics of a Java static initializer
资料:
Kotlin笔记 Object表达式和声明: http://www.jianshu.com/p/f316ff2f4306
Object Expressions and Declarations: https://kotlinlang.org/docs/reference/object-declarations.html