Kotlin学习系列(七)强大的object关键字

本系列内容均来自《Kotlin从小白到大牛》一书,感谢作者关东升老师。
object关键字主要用于声明一个类的同时创建这个类的对象。 具体而言它有三个方面的应用: 对象表达式、 对象声明和伴生对象。

1 对象表达式

object关键字可以声明对象表达式, 对象表达式用来替代Java中的匿名内部类。 就是在声明一个匿名类, 并同时创建匿名类的对象。
对象表达式示例如下:

//声明OnClickListener接口
interface OnClickListener {
    fun onClick()
} 
fun main(args: Array<String>) {
    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<String>) {
//对象表达式赋值
    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<String>) {
//无具体父类对象表达式
    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<Any>?
}

object UserDAO : DAOInterface { ①
    //保存所有数据属性
    private var datas: Array<Any>? = null
    override fun findAll(): Array<Any>? {
//TODO 查询所有数据
        return datas
    }
    override fun create(): Int {
//TODO 插入数据
        return 0
    }
}

fun main(args: Array<String>) {
    UserDAO.create() ②
    var datas = UserDAO.findAll() ③
}

上述代码第①行是对象声明, 声明UserDAO单例对象, 使用object关键字后面是类名。在对象声明的同时可以指定对象实现的接口或父类, 本例中指定实现DAOInterface接口。 在类体中可以有自己的成员函数和属性。 在调用时, 可以通过类名直接访问单例对象的函数和属性, 见代码第②行和第③行。
提示 对象声明不能嵌套在其他函数中, 但可以嵌套在其他类中或其他对象声明中。
示例代码如下:

object UserDAO : DAOInterface {
    //保存所有数据属性
    private var datas: Array<Any>? = null
    override fun findAll(): Array<Any>? {
//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<String>) {
    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<String>) {
    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<String>) {
    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()

从上述代码可见, 调用伴生对象的扩展函数与普通函数访问没有区别。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容