【学习笔记】区别Kotlin中的object和companion object关键字

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

一、前言

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

在 Kotlin 中,除了 object 关键字还有个 companion object 关键字,这个又是什么鬼?怎么使用?有啥区别?在没有仔细阅读相关文档资料之前还真有点傻傻分不清了。实践出真知,在经过简单的练习加上相关博客文章、源码阅读、谷歌搜索后,我心里所认识的 objectcompanion object 是这样的:

  • object 可以定义在全局也可以在类的内部使用
  • object 就是单例模式的化身
  • object 可以实现 Java 中的匿名类
  • companion object 就是 Java 中的 static 变量
  • companion object 只能定义在对应的类中

但是,这些认识都是停留在表面上,在我继续阅读《 Kotlin in Action 》这本书相应章节后,我能发现它们的使用场景和功能点远不止这些!究其原因,主要是我并没有完全弄清楚它们的原理以及它们之间的差别,不论是 object 还是 companion object ,它们的共性和区别还有这些:

  1. object 可以作为变量的定义也可以是表达式
  2. object 匿名类可以继承并超越 Java 中匿名类而实现多个接口
  3. object 表达式当场实例化,但定义的 object 变量是延迟实例化的
  4. object 和 companion object 都可以为其取名也可以隐姓埋名
  5. object 匿名内部类甚至可以引用并更改局部变量
  6. companion object 甚至还可以被扩展
  7. Java 中需要结合 @JvmStatic 和 @JvmField 使用
  8. …… 还有很多异同点等着你的开发

既然这俩兄弟有这么多异同点,那么我觉得非常有必要总结一下,以便将来能够更加得心应手地使用 Kotlin 吧。

二、正文

1. object基本定义

object 可以轻松实现 Kotlin 单例模式, 它可以定义在全局之中,也可以定义在类的内部。但是要注意几点:

  1. object 定义后即刻实例化
  2. 因此 object 不能有定义构造函数
  3. 定义在类内部的 object 并不能访问类的成员
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.")
}
}
}

2. object作为表达式

在 Android 开发中,我们经常会设置一个接口匿名类作为点击事件的参数: setOnClickListener(View.OnClickListener) ,这个时候在 Kotlin 中就可以使用 object 来表达那些匿名内部类。同时 object 相比 Java 更加强大,在用其表达内部类的时候有这几个注意点:

  1. object 继承父类必须立刻传递父类构造参数
  2. object 匿名类可以同时实现多个接口
  3. object 匿名类作为参数并没有名字定义,但是可以为其定义一个变量名,如果实现多接口不能直接用类型推断,拗口吧,请看下面代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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
}
}

3. object可以访问非final局部变量

我们知道在 Java 中,内部类是不可以访问外部的非 final 成员变量的,也就是说:它不允许更改变量值!但是, Kotlin 的 object 可以。看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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

4. companion object使用方法

object 不同, companion object 的定义完全属于类的本身,所以 companion object 肯定是不能脱离类而定义在全局之中。它就像 Java 里的 static 变量,所以我们定义 companion object 变量的时候也一般会使用大写的命名方式。

同时,和 object 类似,可以给 companion object 命名,也可以不给名字,这个时候它会有个默认的名字: Companion ,而且,它只在类里面能定义一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
}

5. 类名可作为接口参数传入

object 还是一样, companion object 也可以实现接口,因为 companion object 寄生于类,甚至还可以直接作为实现了相应得接口的参数形式传入,拗口,看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
}

6. 扩展类的静态成员

Kotlin 的扩展功能非常强大,是程序猿爱不释口且口口相传的实用特性之一。那么我们怎么扩展类的静态成员呢?这个时候当然是 companion object 派上用场的时刻了!

1
2
3
4
5
6
7
8
9
10
11
12
13
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 就是那么强大!不得不服! sunglasses

三、总结

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

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

最后,引用官方文档说明,比较它们的实例化过程:

  • 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


Comments: