本系列内容均来自《Kotlin从小白到大牛》一书,感谢作者关东升老师。
object关键字主要用于声明一个类的同时创建这个类的对象
。 具体而言它有三个方面的应用: 对象表达式、 对象声明和伴生对象。
1 对象表达式
object关键字可以声明对象表达式
, 对象表达式用来替代Java中的匿名内部类
。 就是在声明一个匿名类, 并同时创建匿名类的对象。
对象表达式示例如下:
//声明OnClickListener接口
interface OnClickListener {
fun onClick()
}
fun main(args: Array) {
var i = 10
val v = View()
// 对象表达式作为函数参数
v.handler(object : OnClickListener {
override fun onClick() {
println("对象表达式作为函数参数...")
println(++i)
}
})
}
上述代码第中v.handler函数的参数是对象表达式, object说明表达式是对象表达式, 该表达式声明了一个实现OnClickListener接口的匿名类, 同时创建对象。 另外,在对象表达式中可以访问外部变量, 并且可以修改,
见代码。
对象表达式
的匿名类可以实现接口
, 也可以继承具体类或抽象类, 示例代码如下:
//声明Person类
open class Person(val name: String, val age: Int)
fun main(args: Array) {
//对象表达式赋值
val person = object : Person("Tony", 18), OnClickListener {②
//实现接口onClick函数
override fun onClick() {
println("实现接口onClick函数...")
}
//重写toString函数
override fun toString(): String {
return ("Person[name=$name, age=$age]")
}
}
println(person)
}
上述代码声明一个Person具体类, 代码第②行是声明对象表达式, 该表达式声明实现OnClickListener接口, 且继承Person类的匿名类, 之间用逗号( ,) 分隔
。Person("Tony", 18)是调用Person构造函数。 注意接口没有构造函数, 所以在表达式中OnClickListener后面没有小括号。
有的时候没有具体的父类也可以使用对象表达式
, 示例代码如下:
fun main(args: Array) {
//无具体父类对象表达式
var rectangle = object { ①
// 矩形宽度
var width: Int = 200
// 矩形高度
var height: Int = 300
//重写toString函数
override fun toString(): String {
return ("[width=$width, height=$height]")
}
}
println(rectangle)
}
代码是声明一个对象表达式, 没有指定具体的父类和实现接口, 直接在object后面大括号中编写类体代码。
2 对象声明
单例设计模式( Singleton) 可以保证在整个的系统运行过程中只有一个实例, 单例设计模式在实际开发中经常使用的设计模式。 Kotlin把单例设计模式上升到语法层面, 对象声明将单例设计模式的细节隐藏起来, 使得在Kotlin中使用单例设计模式变得非常的简单。
对象声明示例代码如下:
interface DAOInterface {
//插入数据
fun create(): Int
//查询所有数据
fun findAll(): Array?
}
object UserDAO : DAOInterface { ①
//保存所有数据属性
private var datas: Array? = null
override fun findAll(): Array? {
//TODO 查询所有数据
return datas
}
override fun create(): Int {
//TODO 插入数据
return 0
}
}
fun main(args: Array) {
UserDAO.create() ②
var datas = UserDAO.findAll() ③
}
上述代码第①行是对象声明, 声明UserDAO单例对象
, 使用object关键字后面是类名。在对象声明的同时可以指定对象实现的接口或父类, 本例中指定实现DAOInterface接口。 在类体中可以有自己的成员函数和属性。 在调用时, 可以通过类名直接访问单例对象的函数和属性, 见代码第②行和第③行。
提示 对象声明不能嵌套在其他函数中, 但可以嵌套在其他类中或其他对象声明中。
示例代码如下:
object UserDAO : DAOInterface {
//保存所有数据属性
private var datas: Array? = null
override fun findAll(): Array? {
//TODO 查询所有数据
return datas
}
override fun create(): Int {
// object Singleton { ①
// val x = 10
// }
return 0;
}
object Singleton { ②
val x = 10
}
}
class Outer {
object Singleton { ③
val x = 10
}
}
fun main(args: Array) {
println(UserDAO.Singleton.x)
// object Singleton { ④
// val x = 10
// }
}
上述代码第①行和第④行试图在函数中嵌入Singleton对象声明, 则会发生编译错误
。 代码第②行是Singleton对象声明嵌入在UserDAO对象声明中。 代码第③行是Singleton对象声明嵌入在Outer类中。
3 伴生对象
在Java类有实例成员和静态成员, 实例成员隶属于类的个体, 静态成员隶属于类本身。 例如: 有一个Account( 银行账户) 类, 它有三个成员属性: amount( 账户金额) 、interestRate( 利率) 和owner( 账户名) 。 在这三个属性中, amount和owner会因人而异, 对于不同的账户这些内容是不同的, 而所有账户的interestRate都是相同的。amount和owner成员属性与账户个体有关, 称为“实例属性”, interestRate成员属性与个体无关, 或者说是所有账户个体共享的, 这种变量称为“静态属性”或“类属性”。
01. 声明伴生对象
在很多语言中静态成员的声明使用static关键字修饰, 而Kotlin没有static关键 字, 也没有静态成员, 它是通过声明伴生对象实现Java静态成员访问方式
。
示例代码如下:
class Account {
// 实例属性账户金额
var amount = 0.0
// 实例属性账户名
var owner: String? = null
// 实例函数
fun messageWith(amt: Double): String {
//实例函数可以访问实例属性、 实例函数、 静态属性和静态函数
val interest = Account.interestBy(amt) ①
return "${owner}的利息是$interest"
}
companion object { ②
// 静态属性利率
var interestRate: Double = 0.0 ③
// 静态函数
fun interestBy(amt: Double): Double {
④
// 静态函数可以访问静态属性和其他静态函数
return interestRate * amt
}
// 静态代码块
init {
⑤
println("静态代码块被调用...")
// 初始化静态属性
interestRate = 0.0668
}
} ⑥
}
fun main(args: Array) {
val myAccount = Account() ⑦
// 访问伴生对象属性
println(Account.interestRate) ⑧
// 访问伴生对象函数
println(Account.interestBy(1000.0)) ⑨
}
上述代码第②行~第⑥行是声明伴生对象, 使用关键字companion和object。 作为对象可以有成员属性和函数, 代码第③行是声明interestRate属性, 伴生对象的属性可以在容器类( Account) 外部通过容器类名直接访问, 见代码第⑧行Account.interestRate表达式
, 这种表达式形式与Java等语言中访问静态属性是类似的。 类似代码第④行声明伴生对象函数, 调用该属性见代码第①行和第⑨行。代码第⑤行是伴生对象的init初始化代码块, 它相当于Java中的静态代码, C#中的静态构造函数, 它可以初始化静态属性, 该代码块会在容器类Account第一次访问时调用, 代码第⑦行是第一次访问Account类, 此时会调用伴生对象的init初始化代码块。
注意 伴生对象函数可以访问自己的属性和函数, 但不能访问容器类中的成员属性和函数。 容器类可以访问伴生对象的函数和属性。
02. 伴生对象非省略形式
在上面的示例中事实上省略的伴生对象名字, 声明伴生对象时还可以添加继承父类或实现接口。 示例代码如下:
//声明OnClickListener接口
interface OnClickListener {
fun onClick()
}
class Account {
// 实例属性账户金额
var amount = 0.0
// 实例属性账户名
var owner: String? = null
// 实例函数
fun messageWith(amt: Double): String {
//实例函数可以访问实例属性、 实例函数、 静态属性和静态函数
val interest = Account.interestBy(amt)
return "${owner}的利息是${interest}"
}
companion object Factory : Date(), OnClickListener { ①
override fun onClick() {
}
// 静态属性利率
var interestRate: Double = 0.0
// 静态函数
fun interestBy(amt: Double): Double {
// 静态函数可以访问静态属性和其他静态函数
return interestRate * amt
}
// 静态代码块
init {
println("静态代码块被调用...")
// 初始化静态属性
interestRate = 0.0668
}
}
}
fun main(args: Array) {
val myAccount = Account()
// 访问伴生对象属性
println(Account.interestRate)
println(Account.Factory.interestRate) ②
// 访问伴生对象函数
println(Account.interestBy(1000.0))
println(Account.Factory.interestBy(1000.0)) ③
}
上述代码第①行是声明伴生对象, 其中Factory是伴生对象名, Date()是继承
Date类, OnClickListener是实现该接口。 一旦显示指定伴生对象名后, 在调用时可以加上伴生对象名, 见代码第②行和第③行, 当然省略伴生对象名也可以调用它的属性和函数。
03. 伴生对象扩展
伴生对象中可以添加扩展函数和属性, 示例代码如下:
//伴生对象声明扩展函数
fun Account.Factory.display() {
println(interestRate)
}
...
//访问伴生对象扩展函数
Account.Factory.display()
Account.display()
从上述代码可见, 调用伴生对象的扩展函数与普通函数访问没有区别。