前言
在上一篇文章我们详细的介绍了Kotlin中接口的使用,本篇文章我们继续讲解Kotlin中的基础知识object关键字。有时候,我们需要创建一个对某个类做了轻微改动的类的对象,而不用为之显式声明新的子类。 Kotlin用对象表达式和对象声明处理这种情况。下面我们开始本篇文章的学习~
1.对象表达式
在Java中我们创建一个匿名的对象使用关键字new,而在Kotlin中创建一个匿名的对象我们使用关键字object。下面我们来看一个比较常见的例子,我们给一个View设置点击事件,在Java中我们就会这么写:
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
}
});
而在Kolin中我们使用关键字object来声明一个匿名类,并在该关键字后紧跟着冒号,冒号代表着继承,不可省略。
view.setOnClickListener(object : View.OnClickListener {
override fun onClick(v: View?) {
}
})
当然OnClickListener接口是一个函数式接口,通常我们都会用Lambda表达式的方式来实现,关于函数式接口我们已经在上一篇接口的文章中详细的介绍了,这里不再赘述。如果父类中有构造函数,则必须传递相应的构造函数参数给它,和普通的类继承一样多个父类使用逗号隔开。如下代码示例:
咦?我们在继承Student类的时候编译器却给了错误提示,在类与继承的文章中我们已经介绍了Kotlin中的类默认是final的是不可被继承的,要使一个类可被继承就必须在class关键字前添加open关键字修饰,类Student必须声明为open的它才能被继承。
任何时候,如果我们仅仅只是需要一个对象,并不需要特殊的超类型,我们可以这么写:
// 私有属性,该对象内部的属性或者方法可以在外部作用域中访问
private val point = object {
val x = 100
val y = 100
}
fun main() {
println("x = ${point.x}, y = ${point.y}")
}
// 输出
x = 100, y = 100
这里需要注意的是,匿名对象可以用作只在本地和私有作用域中声明的类型。如果你使用匿名对象作为公有函数的返回类型或者用作公有属性的类型,那么该函数或属性的实际类型会是匿名对象声明的超类型,如果你没有声明任何超类型,就会是 Any。在匿名对象中添加的成员将无法访问。
// 共有属性point返回的是一个Any类型
val point = object {
val x = 100
val y = 100
}
fun main() {
val x = point.x // 错误无法访问属性x
}
```
对象表达式中的代码可以访问来自包含它的外部作用域的变量,如下示例代码:
```
class View {
private val name = "View"
private val point = object {
val x = 100
val y = 100
val parentName: String = name
}
}
2.对象声明
在Kotlin中我们在object关键字后跟上一个名称,我们就称之为对象声明。在Android Studio中选中当前项目,右击鼠标,New -> Kotlin Class/File,在下图的弹窗中我们选择Object:
创建一个名为Controller的对象。如下示例代码:
object Controller {
const val TAG = "Controller"
fun getInfo() { println("getInfo") }
}
就像变量声明一样,对象声明不是一个表达式,不能用在赋值语句的右边。如果我们想要访问该对象中的属性或者方法,可以直接使用ClassName.propertyName或者ClassName.funName。在实际开发中我们常说使用object声明的对象是单例类。那么这个单例类到底是如何实现的呢?在Android Studio中依次打开Tools -> Kotlin -> Show Kotlin Bytecode,在右边的弹窗中我们点击Decompile按钮,我们来看一下上述代码反编译成的Java代码:
由上图中标记的2处和3处我们可以看到,使用object声明的对象在反编译成Java代码时也只是一个普通的class类,只是Kotlin编译器帮我们做了一些额外的工作。首先在Controller类内部定义了一个私有的无参构造函数,并在该类的static代码块中帮我们创建一个Controller对象,并将该对象的引用赋值给了类变量INSTANCE。在Java中,我们知道一个类的静态变量和静态代码块在该类被加载的时候就会调用。当我们再去访问该对象的属性或者方法,事实上就是通过在该类静态代码块中被赋值的INSTANCE变量来访问的。比如我们在Kotlin中通过Controller.getInfo()访问该对象的getInfo方法,反编译成Java代码就是:
Controller.INSTANCE.getInfo()
这些对象也可以有自己的超类型,例如我们使用Controller对象去实现一个接口:
interface Info {
fun getInfo()
}
object Controller : Info {
override fun getInfo() {
// TODO("Not yet implemented")
}
}
对象声明不能在局部作用域(即直接嵌套在函数内部),但是它们可以嵌套到其他对象声明或非内部类中。如下代码示例:
//在声明的DoMain对象中嵌套声明SubDoMain对象。
object DoMain {
const val TAG = "DoMain"
object SubDoMain {
const val TAG = "SubDoMain"
}
}
//在类Student中嵌套声明Info对象。
class Student {
object Info {
const val TAG = "Info"
}
}
3.伴生对象
我们使用companion关键字来标记类内部的对象声明。如下代码示例,我们在类Person中声明一个名为Factory的对象:
class Person private constructor(){
companion object Factory {
const val TAG = "Person"
fun newInstance() = Person()
}
}
可以直接只用外部的类名来访问该伴生对象的属性或者方法:
fun main() {
Person.TAG
val instance = Person.newInstance()
}
通常我们都会省略该伴生对象的名称:
class Person private constructor(){
companion object {
const val TAG = "Person"
fun newInstance() = Person()
}
}
该伴生对象将会拥有一个默认的名称Companion:
fun main() {
val tag = Person.Companion.TAG
val instance = Person.Companion.newInstance()
}
在Android Studio中依次打开Tools -> Kotlin -> Show Kotlin ByteCode,在右边的弹出框中我们点击Decompile按钮:
我们来看一下反编译成Java的代码,伴生对象是如何实现的呢?
由上图中标记的2处我们可以看到伴生对象其实就是在该类内部定义了一个静态内部Companion,也就是该伴生对象的默认类名。我们使用上图中标记的1处也就是该静态内部类的对象实例Companion来访问其内部的方法和属性。由此我们可以看到伴生对象在运行时他们仍是真实的对象,它们也可以继承一个类或者实现接口。如下代码示例,我们使用在类Person内部声明的伴生对象实现了接口Factory。
interface Factory<T> {
fun create() : T
}
class Person {
companion object : Factory<Person> {
override fun create(): Person = Person()
}
}
4.对象表达式和对象声明之间的差异:
- 对象表达式是在使用他们的地方**立即**及初始化的
- 对象声明是在第一次被访问到时**延迟**初始化的
- 伴生对象的初始化是在相应的类被加载时,与 Java 静态初始化器的语义相匹配
总结
对象表达式和对象声明在我们实际开发中是必须要掌握的知识。Kotlin中的语法细节还是非常多的,熟练的掌握每一个小知识点我们才能更好的在实际开发中运用它们。下篇文章我们将继续讲解Kotlin中的基础知识数据类、密封类、枚举类。我们下期再见~