在将Java代码一键转换为Kotlin代码的时候,碰到了kotlin中的伴生对象(companion object
)然后开始查,又发现了对象表达式和对象声明。
有时候,我们想处理异步请求或者回调事件,不想显示声明一个新的子类。在Java中可以用匿名内部类的方式处理这个问题,kotlin可以使用对象表达式或者对象声明。
对象表达式
要创建一个继承自某个类型的匿名类的对象,我们可以这么写:
backTV.setOnClickListener(object : View.OnClickListener{
override fun onClick(v: View?) {
startActivity(Intent(mContext,PrescriptionListActivity::class.java))
}
})
如果是单个方法的接口还可以这么写
examinePrescriptionTV.setOnClickListener{
startActivity(Intent(mContext,NextActivity::class.java))
}
如果超类型有⼀个构造函数,则必须传递适当的构造函数参数给它。 多个超类型可以由跟在冒号后⾯的逗号分隔的列表指定(只能有一个父类):
open class A(x: Int) {
public open val y: Int = x
}
open class A2(x: Int) {
public open val y: Int = x
}
interface B {}
val ab: A = object : A(1), B /**,A2(1) 可以有多个接口,只能有一个父类,和Java一样,不要被迷惑了*/{
override val y = 20
}
任何时候,如果我们只需要“⼀个对象⽽已”,并不需要特殊超类型,那么我们可以简单地写(adHoc
):
fun main(args:Array<String>){
var name = "zhangfei"
val adHoc = object {
var x: Int = 0
var y: Int = 0
fun prilntName(){//对象可以
print(name)
}
}
print(adHoc.x + adHoc.y)
adHoc.prilntName()
}
从上例代码中我们也可以看出:
就像 Java 匿名内部类一样,对象表达式中的代码可以访问包含它的作用域的变量(这些变量不仅仅限制于final变量,Java匿名内部类只能访问final修饰的局部变量或者外部类的成员变量)。
请注意,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any 。在匿名对象中添加的成员将无法访问。
一句话总结是:匿名对象类型只能放在私有作用域中private
中或者参数中,放在公有作用域中public
导致访问不到。
class C {
// 私有函数,所以其返回类型是匿名对象类型
private fun foo() = object {
val x: String = "x"
}
// 公有函数,所以其返回类型是 Any
fun publicFoo() = object {
val x: String = "x"
}
private var student1 = object {
val name1:String = "n1"
val score1:String = "s1"
}
public var student2 = object {
val name2:String = "n1"
val score2:String = "s1"
}
/**
* 测试公有函数和私有函数返回匿名对象的处理:公有方法无法访问匿名对象里面的属性
*/
fun bar() {
val x1 = foo().x // 没问题
val x2 = publicFoo().x // 错误:未能解析的引⽤“x”
}
/**
* 测试共有属性与私有属性值都是匿名对象的处理区别:公有有属性无法访问匿名对象里面的属性
*/
fun testField(){
student1.name1
student2.name2 //错误:未能解析的引用name2
}
//测试伴生对象 每个类里面只有一个伴生对象
companion object MM{
}
}
对象声明
单例在kotlin中非常容易
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
这称为对象声明。并且它总是在 object 关键字后跟⼀个名称。 就像变量声明⼀样,对象声明不是⼀个表达式,不能⽤在赋值语句的右边。
要引⽤该对象,我们直接使⽤其名称即可: DataProviderManager.registerDataProvider(……)
这些对象可以有超类型:
object DefaultListener : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ……
}
override fun mouseEntered(e: MouseEvent) {
// ……
}
}
注意:对象声明不能在局部作⽤域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或⾮内部类中。
伴生对象
类内部的对象声明可以companion
关键字标记:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
该伴⽣对象的成员可通过只使⽤类名作为限定符来调⽤: val instance = MyClass.create()
可以省略伴⽣对象的名称,在这种情况下将使⽤名称 Companion
:
class MyClass {
companion object {
}
}
val x = MyClass.Companion
请注意,即使伴⽣对象的成员看起来像其他语⾔的静态成员,在运⾏时他们仍然是真实对象的实例成员,⽽且,例如还可以实现接⼝:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {
override fun create(): MyClass = MyClass()
}
}
当然,在 JVM 平台,如果使⽤ @JvmStatic
注解,你可以将伴⽣对象的成员⽣成为真正的静态⽅法和字段。更详细信息请参⻅Java互操作性⼀节 。
对象表达式与对象声明之间的语义差别
对象表达式和对象声明之间有一个重要的语义差别:
- 对象表达式是在使用它们的地方立即执行(及初始化)的
- 对象声明是在第一次被访问到时延迟初始化的
- 伴生对象的初始化是在相应的类被加载(解析)时,与 Java 静态初始化器的语义相匹配