Kotlin中的object关键字
有时候,实现某个功能时,需要对某个类进行一些小的改动,然而完整地定义一个子类去继承往往显得过于繁琐,因为我们可能只想用那么一次,这个时候就可以用到object
关键字
主要包括以下三种用法:
- 对象表达式
- 对象声明
- 伴生对象
1. 对象表达式
首先,object
可以直接用的
val something = object {
val name = "Jack"
val age = 10
override fun toString(): String {
return "name=$name age=$age"
}
}
这里的object
后面直接是一个代码块,这代表着一个类的对象,从外观上看,它没有继承任何类
但是,实际上它是Any
的子类,因此这里的toString()
是覆写了原来Any
的toString()
因此还可以看到其他可以覆写的Any
类的方法
这里使用object
定义的同时进行了实例化,尽管该类没有名字,但是可以借助something变量指向,像其他类一样使用
当然在多数情况下,我们不直接继承Any
,往往我们需要继承某个具有特定功能的类或者是实现某些接口,用来组合业务需求
open class MyClickListener {
open fun onClick() {
println("click...")
}
}
比如说,我们需要基于这个类的点击功能,自定义满足某个特殊需求的功能,这个时候我们一般需要继承,然后覆写这个点击的功能,然而这个实现可能只会被用到一次,这在Android开发中很常见
val listener = object : MyClickListener() {
override fun onClick() {
println("click me...")
}
}
右边的内容就是经常能看到的点击事件的形式,object
可以把它看成一个占位的,它代表一个类,它的特点是继承了MyClickListener,我们可以通过覆写指定的方法完成自己的逻辑,而右边这个匿名的类的结构是透明的,整个右边同样是包含了定义和实例化
object
描述了与父类的继承关系,这种地位可能比较卑微,就像是有血缘关系但是没有名分
随着项目业务的扩展,往往继承的功能也就越多,这个时候可能需要继承的内容就不止一个类
interface NetChangeListener {
fun onChanged(hasNet: Boolean)
}
再增加一个接口
val listener = object : MyClickListener(), NetChangeListener {
override fun onClick() {
println("click me...")
}
override fun onChanged(hasNet: Boolean) {
// todo
}
}
定义的时候可以用逗号隔开,需要覆写的方法可以统一放到代码块当中,因为这正是我们需要用到的方法集
2. 访问
将匿名表达式用作函数的返回值,这个时候自然就是定义的同时完成实例化
此时,有一些规则:
- 一个匿名对象如果是一个局部或
private
的类型,并且为非内联的,那么这个匿名对象代指的内容的成员,可以通过当前的这个途径获取到
class C {
private fun getObject() = object { // private类型的接收,这里的类型会为匿名类型
val x: String = "x" // 匿名类的成员
}
fun printX() {
println(getObject().x) // 满足条件,可以访问
}
}
这里的类型是作为Any
类的一个匿名子类,就是它本身,因此可以轻松拿到其成员
- 在
public
和private inline
的场景中,真实的类型判断如下:- 匿名对象没有声明父类的情况下,判断为
Any
- 声明父类的取父类的类型
- 匿名对象没有声明父类的情况下,判断为
interface A {
fun funFromA() {}
}
interface B
class C {
// 返回Any
fun getObject() = object { // public 向上推父级,Any
val x: String = "x"
}
// 返回父类A
fun getObjectA() = object: A { // 能见光的是A,object是私生子
override fun funFromA() {}
val x: String = "x"
}
// 多个父级,需要指定
fun getObjectB(): B = object: A, B { // B类型
override fun funFromA() {}
val x: String = "x"
}
}
object
如同只是一个小圈子里(private)的称谓(匿名对象),但是在外面(public)仍然需要更加体面的身份(父级)
3. 对象声明
单例模式在实际运用中还是很常见的,而Kotlin提供了更加便捷的使用方式
object ConfigManager {
fun getXMLByName() {
// todo
}
}
使用object
代替class
直接就完成了单例类的定义
并且单例类会在第一次访问时初始化,该过程是线程安全的
ConfigManager.getXMLByName()
直接使用类名加方法名就可以调用方法了
并且,单例类也可以继承,这与之前的object
表达式相似
object AdvancedClickListener : MyClickListener() {
override fun onClick() {
super.onClick()
}
}
4. 伴生对象
把刚刚的对象声明放到一个普通的类的内部,再标记上companion
,这就成了伴生对象
class WebManager {
companion object UrlHelper {
fun getBaseUrl() = "http://www.baidu.com"
}
}
在具体使用时,可以直接使用类名结合伴生对象的成员
WebManager.getBaseUrl()
WebManager.UrlHelper.getBaseUrl() // 没有问题,只是通常省略伴生对象的类名
类可以访问到位于自己内部的伴生对象的私有成员
class WebManager {
fun log() {
println("protocol=$PROTOCOL") // 可以获取到
}
companion object UrlHelper {
private const val PROTOCOL = "http://" // 私有的伴生对象成员
fun getBaseUrl() = "${PROTOCOL}www.baidu.com"
}
}
相信伴生对象这样的用法会让人不经意联想到Java中的静态成员,然而这二者并不完全一样
在运行时,这些伴生对象的成员依然是属于实例而非类
如果想要生成真正的基于JVM的静态成员,可以针对于字段和方法分别应用@JvmField
和@JvmStatic