- 对象表达式
- 对象声明 (单例,隶属于——饿汉模式)
- 伴生对象
object 关键字主要在声明一个类的同时创建这个类的对象。具体而言它有三方面应用:对象表达式、对象声明 和 伴生对象。
一、对象表达式
- object 关键字可以声明对象表达式,对象表达式用来替代 Java 中的匿名内部类。
class View {
fun handler(listener: OnClickListener?) {
listener?.onClick()
}
}
interface OnClickListener {
fun onClick()
}
fun main(args: Array<String>) {
var i = 0;
val view = View()
view.handler(object :OnClickListener {
override fun onClick() {
println("对象表达式作为函数参数... i=$i")
i++
}
})
}
上述代码中 view.handler
函数的参数是对象表达式,object 说明表达式是对象表达式,该表达式声明了一个实现 OnClickListener
接口的匿名类,同时创建对象。
- 对象表达式的匿名类可以实现接口,也可以继承具体类或抽象类。
open class Person(val name: String, var age: Int) // 1️⃣
fun main(args: Array<String>) {
val person = object :Person("小三", 18), OnClickListener { // 2️⃣
override fun onClick() {
println("实现接口 onClick 函数...")
}
override fun toString(): String {
return "[name: $name, age: $age]"
}
}
person.onClick()
println(person)
}
2019-05-30 18:41:17.171 31741-31741/cn.ak.kot I/System.out: 实现接口 onClick 函数...
2019-05-30 18:41:17.171 31741-31741/cn.ak.kot I/System.out: [name: 小三, age: 18]
上面代码第1️⃣行是声明一个Person的具体类,代码第2️⃣行是声明对象表达式,该表达式声明实现了 OnClickListener
接口,且继承 Person 类的匿名类,之间用逗号 (,) 分隔。Person("小三", 18)
是调用 Person 的构造函数。
- 没有具体的父类也可以使用对象表达式。
fun main(args: Array<String>) {
val rect = object {
val x: Int = 10
val y: Int
init {
y = 10
}
fun area() = x * y
override fun toString(): String {
return "[x:$x, y:$y]"
}
}
println(rect.area()) // 100
println(rect) // [x:10, y:10]
}
二、对象声明 (单例)
单例设计模式可以保证在整个的系统运行过程中只有一个实例,单例模式在开发中是经常使用的设计模式,所以kotlin把单例设计模式上升到语法层面。
- 单例对象声明,object 关键字后面是类名,在对象声明的同时可以指定对象实现接口或父类,在类体中可以有自己的成员函数和属性,在调用时,可以通过类名直接访问单例对象的函数和属性。
open class EnglishTool(var name: String) {
override fun toString(): String {
return "[name: $name]"
}
}
interface Upper {
fun toUpper(word: String): String
}
object Toolkit: EnglishTool("英语"), Upper {
override fun toUpper(word: String): String {
return word.toUpperCase()
}
fun printUpperWord(word: String) {
println(toUpper(word))
}
}
fun main(args: Array<String>) {
Toolkit.printUpperWord("hello ak")
println(Toolkit)
Toolkit.name = "中文"
println(Toolkit)
}
2019-05-30 19:12:01.819 3724-3724/cn.ak.kot I/System.out: HELLO AK
2019-05-30 19:12:01.819 3724-3724/cn.ak.kot I/System.out: [name: 英语]
2019-05-30 19:12:01.819 3724-3724/cn.ak.kot I/System.out: [name: 中文]
- 为什么是饿汉模式?
// kotlin 单例代码
object Toolkit {
}
// kotlin 转换后的 java 代码
package cn.ak.kotmodule;
import kotlin.Metadata;
@Metadata(
mv = {1, 1, 15},
bv = {1, 0, 3},
k = 1,
d1 = {"\u0000\f\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\b\u0002\bÆ\u0002\u0018\u00002\u00020\u0001B\u0007\b\u0002¢\u0006\u0002\u0010\u0002¨\u0006\u0003"},
d2 = {"Lcn/ak/kotmodule/Toolkit;", "", "()V", "kotlin_module"}
)
public final class Toolkit {
public static final Toolkit INSTANCE;
private Toolkit() {
}
static {
Toolkit var0 = new Toolkit();
INSTANCE = var0;
}
}
从 kotlin 解释出的 java 很直白的可以出是一个 饿汉模式。至于怎么查看 kotlin 解释出的 java 代码,请自行百度,本文中是开发工具是 Android Studio。
- object 对象声明不能嵌套在其他 函数 中, 但可以嵌套在其他 类 或 其他 object对象声明 中。
object Toolkit {
fun doSomethings(): 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(Toolkit.Singleton.x) // 10
println(Outer.Singleton.x) // 10
}
三、伴生对象
在很多语言中静态成员的声明使用 static 关键字修饰,而 kotlin 没有 static 关键字,也没有静态成员,它是通过声明伴生对象实现 Java 静态成员的访问方式。
1、声明伴生对象
class Account {
var amout = 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 { // 1️⃣
println("静态代码块被调用")
// 初始化静态属性
interestRate = 0.0668
}
}
}
fun main(args: Array<String>) {
val account = Account() // 2️⃣
// 访问伴生对象属性
println(Account.interestRate)
// 访问伴生对象函数
println(Account.interestBy(1000.0))
}
- 伴生对象,使用关键字 companion 和 object 声明。作为对象可以有成员属性和函数,在容器类外访问伴生对象的属性和函数,可以通过容器类名直接访问。代码第1️⃣行是伴生对象的 init 初始化代码块,它相当于 Java 中的静态代码,它可以初始化静态属性,该代码块会在容器类第一次访问时调用,代码第2️⃣行是第一次访问
Account
类,此时会调用伴生对象的 init 初始化代码块。
注意:伴生对象函数可以访问自己的属性和函数,但不能访问容器类中的成员属性和函数。容器类可以访问伴生对象的函数和属性。这一点与 Java 类中的静态成员和静态方法是一样的,只能访问本类的静态成员和其它的静态函数。
2、伴生对象非省略形式
上面示例中,事实上省略了伴生对象的名字,声明伴生对象时还可以添加继承父类或实现接口。
open class Text(var name: String) {
override fun toString(): String {
return "[name: $name]"
}
}
class GView {
companion object GButton : Text("按钮"), OnClickListener {
override fun onClick() {
println("点击了伴生对象的onClick...")
}
init {
println("伴生对象初始化代码块")
name = "View的伴生对象Button的成员属性name=按钮"
}
}
}
fun main(args: Array<String>) {
// val v = GView()
println(GView.name)
println(GView.GButton.name)
GView.onClick()
GView.GButton.onClick()
}
2019-05-30 23:44:45.237 12893-12893/com.ktln.cltor I/System.out: 伴生对象初始化代码块
2019-05-30 23:44:45.237 12893-12893/com.ktln.cltor I/System.out: View的伴生对象Button的成员属性name=按钮
2019-05-30 23:44:45.237 12893-12893/com.ktln.cltor I/System.out: View的伴生对象Button的成员属性name=按钮
2019-05-30 23:44:45.237 12893-12893/com.ktln.cltor I/System.out: 点击了伴生对象的onClick...
2019-05-30 23:44:45.238 12893-12893/com.ktln.cltor I/System.out: 点击了伴生对象的onClick...
讲解:上面代码中 GButton
是伴生对象名,Text("按钮")
是继承 Text
类,OnClickLisener
是实现该接口。一旦显示指定伴生对象名后,在调用时可以加上伴生对象名,当然省略伴生对象名也可以调用它的属性和函数。
3、伴生对象扩展
伴生对象中可以添加扩展函数和属性。
val GView.GButton.length: Int
get() = this.name.length
fun GView.GButton.display() {
println("[name: $name, length: $length]")
}
fun main(args: Array<String>) {
// 访问伴生对象扩展属性
println(GView.GButton.length)
println(GView.length)
// 访问伴生对象扩展函数
GView.GButton.display()
GView.display()
}
2019-05-31 00:26:43.813 17030-17030/com.ktln.cltor I/System.out: 伴生对象初始化代码块
2019-05-31 00:26:43.814 17030-17030/com.ktln.cltor I/System.out: 27
2019-05-31 00:26:43.814 17030-17030/com.ktln.cltor I/System.out: 27
2019-05-31 00:26:43.814 17030-17030/com.ktln.cltor I/System.out: [name: View的伴生对象Button的成员属性name=按钮, length: 27]
2019-05-31 00:26:43.814 17030-17030/com.ktln.cltor I/System.out: [name: View的伴生对象Button的成员属性name=按钮, length: 27]
从上述代码可见,调用伴生对象的扩展函数与普通函数访问没有区别。