前言:现实会告诉你,不努力就会被生活踩死,无需找什么借口,一无所有就是拼的理由。
一、概述
有时候我们需要创建一个对某些类稍加修改的对象,而不必显式地成为它声明一个新的子类。Kotlin 用对象表达式和对象声明来处理这种情况。使用 object 关键字能实现类似 Java 中的匿名内部类功能。Kotlin 提供 object 关键字的用意就是,在生成在一个对当前类进行轻微修改的类对象,且不需要声明一个新的子类的时候使用。
下面将从 object 作为表达式和作为声明两部分阐述一下 object 的用法。
二、对象表达式
创建一个匿名类的对象,object 实际上是作为表达式存在,产生的是 object 类型的匿名对象:
interface OnClickListener {
fun onClick(v: View?)
}
private fun setOnClickListener(listener: OnClickListener?) { … }
//调用
fun test() {
setOnClickListener(object : OnClickListener {//匿名内部类
override fun onClick(v: View?) {
}
})
}
如果超类有一个构造函数,生成的匿名对象则必须将适当的构造函数参数传递给它。如果有多个超类型可以在冒号后面指定一个逗号分隔:
open class People(name: String) {}//父类有构造函数
fun test() {
val people: OnClickListener = object : People("Android"), OnClickListener {
override fun onClick(v: View?) {
//TODO
}
}
}
注意:如果父类构造方法有入参,在生成匿名对象时必须按照父类构造方法的定义传参;如果生成的匿名函数有多个父实现,在声明 object 时必须指定类型,如 val people: OnClickListener
。
如果在任何情况下,我们需要的仅仅是一个对象,并没有特别的超类,可以简单为:
fun test() {
val add = object {//没有任何超类,只是作为一个对象存在
val numA: Int = 1
val numB: Int = 2
}
println("numA + numB = ${add.numA + add.numB}") //打印数据:3
}
匿名对象只能在本地和私有声明中作为类型使用。如果使用匿名对象作为公共函数的返回类型或公共属性的类型,该函数或属性的实际类型将是匿名对象声明的超类型,如果没有声明任何超类型,则为 Any 。添加到匿名对象中的成员将不可访问。
//私有函数,返回类型为匿名对象类型
private fun privateFoo() = object {
val str: String = "private"
}
//公有函数,返回对象为超类型,如果没有超类则为Any
public fun publicFoo() = object {
val str: String = "public"
}
fun test() {
val str1 = privateFoo().str // compile success,private修饰的匿名 object 返回的类型是 object 类型
val str2 = publicFoo().str // compile error,public 修饰的匿名 object 返回的类型是其超类,如果没有超类则是 Any,而 Any 类中没有 str 字段
}
匿名 object 作为函数或属性存在时有一定的限制:
- 匿名 object 为 private 修饰时,其表达式返回的类型就是 object 类型;
- 匿名 object 为 public 修饰时,其表达式返回的类型是其超类的类型,如果没有超类则为 Kotlin 中顶层的 Any类型。
Kotlin 允许在匿名类对象内部访问外部的成员,与 Java 不同的是此时外部成员不必再声明为 final (Kotlin 的val)。
var name: String = "Kotlin"
fun test() {
setOnClickListener(object : OnClickListener {
override fun onClick(v: View?) {
println("匿名object对象访问外部成员:name = $name")//打印:Kotlin
}
})
}
此时使用外部的 name 属性,也不需要声明为不可变。
三、对象声明
单例可能在一些情况下有用,Kotlin(在Scala之后)使声明单例变得简单。在 Kotlin 中,可以不需要像 Java 那样自己去实现单例,而是提供关键字来保证单例,这个关键字就是 object。当 object 修饰一个 class 时,这个类就只有一个对象。
object DataProviderManager {//使用object来声明一个单例对象
fun registerDataProvider(str: String) {
//TODO
}
}
这称为对象声明,它总是在 object 关键字后面有一个名称。就像变量声明一样,对象声明也不是表达式,不能在赋值语句的右侧使用。
对象声明的初始化是线程安全的,并在第一次访问时完成。不需要像 Java 那样考虑多线程的情况。
如果要引用对象,我们直接使用它的名称:
DataProviderManager.registerDataProvider("")
这样的对象可以有超类型:
abstract class MyAdapter {
open fun mouseClicked() { }//open 修饰,子类可重写
}
fun test() {
object : MyAdapter() {
override fun mouseClicked() { }
}
}
注意:对象声明不能是本地的(即直接嵌套在函数中),但可以嵌套到其他对象声明或非内部类中。
object 作为声明时需要注意一下几点:
- object 作为声明时没有右值,即无法作为表达式那样赋值给其他变量;
- object 作为声明时无法声明本地变量,但可以作为类成员存在;
- object 作为声明时可以继承超类,即单例可以有超类。
伴生对象
类内的对象声明可以用 companion 关键字进行标记:
class Student {
companion object newInstance {//伴生对象
fun create(): People = People("ALL")
}
}
可以简单地使用类名作为限定符来调用伴生对象的成员:
val instance = Student.create()
伴生对象的名称可以省略,在这种情况下,将使用名称 Companion
来调用:
class Student {
companion object { }
}
val companion = Student.Companion
类自身使用的名称(不作为另一名称的限定符)作为类的伴生对象的引用(无论命名与否)。即无论伴生对象有没有命名,只要使用包含伴生对象的类名进行赋值时,此值实际上就是该类所持有的其内部伴生对象的引用地址:
class Student {
companion object Named{ }
}
val student1 = Student
class Student {
companion object { }
}
val student2 = Student
注意:即使伴生对象的成员在其他语言中看起来像静态成员,在运行时它们仍然是真实对象的实例成员,例如,可以实现接口:
interface Factory<T> {
fun create(): T
}
class MyClass {
companion object : Factory<MyClass> {//实现接口
override fun create(): MyClass = MyClass()
}
}
val factory: Factory<MyClass> = MyClass
但是,在 JVM 上,如果使用 @JvmStatic
注释,可以将伴生对象的成员生成为真正的静态方法和字段,即使用 @JvmStatic
和 Java 中的 static 成员一样。比如,我们使用的 main 方法都是使用 @JvmStatic
注释的,因为 Java 的 main 方法必须是 static 的,而伴生对象的成员不是静态的,所以需要额外添加 static 修饰符告诉编译器,这是一个静态方法。
对象表达式和声明之间的语义差异
对象表达式和对象声明之间有一个重要的语义区别:
- 在使用对象表达式的地方,立即执行(并初始化)对象表达式;
- 第一次访问时,对象声明被惰性地初始化;
- 在加载(解析)相应的类时初始化一个伴生对象,与 Java 静态初始化器一样。
源码地址:https://github.com/FollowExcellence/KotlinDemo-master
点关注,不迷路
好了各位,以上就是这篇文章的全部内容了,能看到这里的人呀,都是人才。
我是suming,感谢各位的支持和认可,您的点赞就是我创作的最大动力,我们下篇文章见!
如果本篇博客有任何错误,请批评指教,不胜感激 !
要想成为一个优秀的安卓开发者,这里有必须要掌握的知识架构,一步一步朝着自己的梦想前进!Keep Moving!