kotlin中的 object 关键字
kotlin 中的object
关键字用处比较广泛,在官方文档对象表达式与对象声明有详细的介绍,比如:创建匿名对象、创建匿名内部类并继承某个类,实现多个接口、使用匿名对象作为返回和值类型、从匿名对象访问变量、单例模式、数据对象、伴生对象等,不过文章是从对象表达式
和对象声明
角度来区分的。
对象表达式
对象表达式可以用来创建匿名类,就是不用class
来声明的类,当这个类只用一次的时候是很有帮助的。我们可以从头开始定义匿名类,也可以从现有类继承,还可以实现接口。匿名类的实例也称为匿名对象,因为它们是由表达式而不是名称定义的。
创建匿名类
1 |
|
可以看到,这种方式在某种意义上和 js 中创建对象差不多,helloWorld
这个实例的helloWorld.javaClass.simpleName
是空的。当然了匿名类也是类,只是没有名字而已,当然做了继承其他类,实现其他接口。注意,同样只能单继承多实现,并且父类构造函数需要参数时可以传适当的构造参数。
比如这样:
1 |
|
当然也可以直接当成参数传入调用的方法,都是一样的。本质上都是创建了一个对象。
使用匿名对象作为返回值或类型
当匿名对象被用作局部或私有但非内联声明(函数或属性)的类型时,其所有成员都可通过该函数或属性访问:
1 |
|
如果方法供或者属性是 public 或者 private inline 时,它的实际类型可能如下:
- 如果没有明确声明类型,则是
Any
- 如果只有一个父类或者一个接口,则是改父类或者接口类型
- 如果有一个父类和多个接口,则是方法明确返回的类型
比如1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26class CC {
// 返回值类型是 Any; 不能在其他方法中访问 x
fun getObject() = object {
val x: String = "x"
}
// 返回值类型是 AA; 不能在其他方法中访问 x
fun getObjectAA() = object: AA {
override fun funFromA() {}
val x: String = "x"
fun test(){
//这里会报错,访问不到 x 属性
println(getObject().x)
}
}
// 返回值类型是 BB; 不能在其他方法中访问 x
fun getObjectBB(): BB = object: AA, BB { // explicit return type is required
override fun funFromA() {}
val x: String = "x"
fun test(){
//这里会报错,访问不到 x 属性
println(getObject().x)
}
}
}匿名对象访问变量
对象表达式中的代码可以访问来自包含它的作用域的变量:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15class AAA{
val x = "x"
val y = "y"
fun getObject() = object {
val xx = x//不报错
val yy = y//不报错
}
class AAAA{
val xx = x //报错
}
object BBBB{
val xx = x//报错
}
}
对象声明
实现单例模式
在Kotlin当中,要实现单例模式
其实非常简单,我们直接用object
修饰类即可,当然这个单例类也是可以有父类的
1 |
|
调用的时候直接类名.方法名
即可。这里有个注意点:对象声明不能在局部作用域(即不能直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。
数据对象(data object)
当我们想要打印object
对象声明时,字符串表示同时包含其名称和对象的哈希:
1 |
|
我们可以还用data
关键字来修饰它
1 |
|
这样的话,编译器会为这个对象生成toString()
方法,该方法只会返回对象名字。还有成对出现的equals()/hashCode()
.这里有一点需要注意:
被重写的equals()
方法会将所有相同名字的data object
都返回true
,这个解释不是太严谨,因为绝大部分情况下,data object
是单例的,在运行时只会有一个对象,但我们可以通过平台相关的方法创建另外一个实例对象,比如 jvm 上的反射、序列化和反序列化等。因此,在比较data object
是否相同时,请使用==
而不是===
data class 和 data object 的不同
虽然数据类和数据对象经常一起使用,并且有一些相同点,但有些函数在data object
中是不会生成的
- 没有
copy()
方法,因为data object
用作单例对象,所以不会生成该方法。如果允许创建另外个实例对象,则违反了该模式。 - 没有
componentN()
方法,该方法的一个简单的用法就是用于结构对象,允许一次性获取对象的所有属性值,并将它们作为单独的参数传递给函数或构造器。但由于data object
没有属性,生成这些方法是没有意义的。
伴生对象
在kotlin
中并没有static
关键字,那么我们如何实现静态方法的效果?我们可以使用companion
和object
关键字达到这个效果
1 |
|
但是看反编译之后的代码,编译器还是为我们创建了A
和AA
两个类。如果在jvm
平台,我们可以使用@JvmStatic
注解,将伴生对象的成员生成为真正的静态方法和字段。
1 |
|
当然,对于上面MyClassOne
中的AA
是可以省略名字的
1 |
|
请注意,即使伴生对象的成员看起来像其他语言的静态成员,在运行时他们仍然是真实对象的实例成员,而且,例如还可以实现接口:
1 |
|
对象表达式和对象声明之间的语义差异
对象表达式和对象声明之间有一个重要的语义差别:
- 对象表达式是在使用他们的地方立即执行(及初始化)的。
- 对象声明是在第一次被访问到时延迟初始化的。
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配 。
参考:
对象表达式与对象声明
Object expressions and declarations
静态字段
已学习:
扩展
- 扩展函数
- 扩展属性
- 作用域
函数类型
- 带有接收者的函数类型
- Lambda表达式
- SAM 转换
泛型
- 逆变
- 协变
- 类型投影
- 星投影
- 泛型约束
关键字
- 作用域函数:with、let、run、apply、also
- object:匿名内部类、单例模式、伴生对象
委托
- 委托类
- 委托属性
- 自定义委托
未学习:
关键字
- Unit
- Nothing
- inline,noinline,crossinline
协程
- 启动
- 挂起
- Job
- Context
- Channel
- Flow
- select
- 并发、异常
- launch
- Dispatchers
- CoroutineScope