Object expressions and declarations
https://kotlinlang.org/docs/object-declarations.html
这边会介绍到什么是
Object expressions
Object declarations
还有什么是
anonymous Object expressions
以及他的衍生,也是大家常用的类似static class宣告方式
Companion Object
anonymous Companion Object
今天在Twitter(x)上看到有一个的发文,他说他在一场会议看到了一个写法
Creating anonymous objects from scratch
val helloWorld = object { val hello = "Hello" val world = "World" // object expressions extend Any, so `override` is required on `toString()` override fun toString() = "$hello $world"}println(helloWorld.hello)
就可以得到
Hello
会很讶异的是,匿名类别
我们知道,但没想到除了宣告匿名类别
外,居然可以额外添加成员
与功能
?
其实后来仔细想一想,本来就可以,你一定也使用过匿名类别的产生,差别在于这边会多上一个:
去实做这个OnViewClickListener
,但是确不知道其实也可以用来产生一个没实作其他类别的物件写法,可以看下面宣告方式,你一定很熟悉
view.setOnClickListener(object: OnViewClickListener() { ...})
然而你会发现,以前也不知道为什么,就是固定写object :
这样方式,来匿名实做介面使用,但其实这是有使用原理的
先看看官方对于Object express
的描述
Object expressions create objects of anonymous classes, that is, classes that aren't explicitly declared with the class declaration. Such classes are useful for one-time use. You can define them from scratch, inherit from existing classes, or implement interfaces. Instances of anonymous classes are also called anonymous objects because they are defined by an expression, not a name.
当中提到,类别不是明确的宣告于某一个类别中,这样的类别在一次性的时候是有用的
. 你可以定义他在来继承存在的类别或者介面实作。匿名类别的实例被称为匿名物件
,因为他们被定义成一种表示
,而非一个名称
有这样定义就更明确的,可以往下看
Inheriting anonymous objects from supertypes
其实就是这个章节提到,也称为Inheriting anonymous objects from supertypes
,官方的範例是这样
window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { /*...*/ } override fun mouseEntered(e: MouseEvent) { /*...*/ }})
并且他也可以多重实作或继承,官方的範例是这样
open class A(x: Int) { public open val y: Int = x}interface B { /*...*/ }val ab: A = object : A(1), B { // <-----同时匿名实作或继承A与B override val y = 15}
也就是Android开发者常见,平常都在使用的匿名实做OnViewClickListener
的写法,因此如果今天我们不进行介面实作,他就会变成anonymous Object expressions
,也就是刚刚最上面的写法
val helloWorld = object { val hello = "Hello" val world = "World" // object expressions extend Any, so `override` is required on `toString()` override fun toString() = "$hello $world"}println(helloWorld.hello)
仔细比对一下,是不是很熟悉,也不会觉得这样写法很特别了,
但你可能会问
Q.如果没有实作介面
或者继承
,会有什么原因会需要使用这样的写法?
个人也是没用过的(毕竟我也才刚了解),但根据官方文件,如果你是one-time use,或者你只是要在某一个function scope中定义一个有属性有方法的匿名类别,就很适合这样使用,我想到的就是你在一个function中刚好要处理一个複杂的case,也懒得去独立建立一个data class然后还要想一个命名给他的时候,就非常适合用匿名物件(anonymous object)
,譬如类似这样,但这可能不是一个很好的範例,等我想到再补充,总之你可以在一个function中去再次整理你的程式码,聚合,当然用透过inner function也是可以
fun test(val outterName: String,val outterAge: Int? = null) { // 在一个function裏面,有多个栏位想要放在一起 // 但又不想额外宣告一个private fun来增加本身类别的function 暴露 val result = object { val name = outterName val age = outterAge ?: "Uknow" fun getDisplay() = "$name -($age)" } // 最终直接这样使用 textView.text = result.getDisplay()}
Accessing variables from anonymous objects
另外是如果在同一个scope中,匿名物件是可以直接去存取同一个scope的变数
fun countClicks(window: JComponent) { var clickCount = 0 // <-----------在fun scope中的变数 var enterCount = 0 window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { clickCount++ // <------------在匿名物件中可以直接读取或者修改是没问题的 } override fun mouseEntered(e: MouseEvent) { enterCount++ } }) // ...}
如同上面,他可以直接存取上面的clickCount
的变数
Object declarations 物件宣告
这写法应该是大家都用过的,也就是java的static class要转成kotlin的时候都会透过object declarations
来宣告使用,并且具有Singleton
的特性,与java static class又不同,他是可以被当成参数传递的
object DataProviderManager { fun registerDataProvider(provider: DataProvider) { // ... } val allDataProviders: Collection<DataProvider> get() = // ...}
上面的方法就称为object declaration
,中文应该就可以称为物件宣告
吧? 这样的方式就不是匿名物件,当然他也可以用来实作或继承其他类别
object DefaultListener : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { ... } override fun mouseEntered(e: MouseEvent) { ... }}
所以其实我觉得官方文件应该是要先介绍这个,然后可以再提到他如果省略前方的object name,就可以变成匿名物件,应该会比较好理解
Data objects 资料物件
另外他也可以像是data class
一样变成data objects
data object MyDataObject { val x: Int = 3}fun main() { println(MyDataObject) // MyDataObject println(MyDataObject.x) // 3}
但差别在于,因为物件实例没办法有建构子
,所以也不会有所谓的建构子参数
,比较适合用来宣告成一个具有不可变的常数物件的感觉
Companion objects
这个应该也是很常见的一个语法,也就是在class中另外宣告一个Companion objects
,可以匿名
,也可以进行命名,而中文好像还没有官方的正式命名,可以称为伴生、协同、共生于class上的物件名称,也就是他寄身于于本的class name上,譬如下方式MyClass
,他就会存在于MyClass
中,所以这就看自己怎么翻译理解啰
class MyClass { companion object Factory { fun create(): MyClass = MyClass() }}
透过这样产生实例
val instance = MyClass.create()
也可以透过Companion
取出Companion objects
class MyClass { companion object { }}val x = MyClass.Companion
接着你也可以不命名,变成匿名Companion objects
class MyClass1 { companion object Named { }}val x = MyClass1class MyClass2 { companion object { }}val y = MyClass2
另外比较少见的,Companion objects
也可以实作另一个介面在
interface Factory<T> { fun create(): T}class MyClass { companion object : Factory<MyClass> { override fun create(): MyClass = MyClass() }}val f: Factory<MyClass> = MyClass
总整理
以上就是关于官方文件谈到的Object expressions and declarations
的章节内容,有很多在书本上没有提到的部分,或者大部分都没看过的额外一些用法,在来整理一下
Object
使用方式val manager = object { fun registerDataProvider(provider: DataProvider) { // ... } val allDataProviders: Collection<DataProvider> get() = // ...}
使用
manager.registerDataProvider(..) // 直接呼叫使用
或是
实做另一个介面,有物件名称view.setMouseAdapter(object: MouseAdapter { override fun mouseClicked(e: MouseEvent) { /*...*/ } override fun mouseEntered(e: MouseEvent) { /*...*/ } fun registerDataProvider(provider: DataProvider) { // ... } val allDataProviders: Collection<DataProvider> get() = // ...})
这样应该就会更清楚了,可以直接弄懂Object declared
后了解什么是Object expression
,我个人也蛮喜欢到官方网站去挖宝的,下次有看到其他觉得特别或者少见的语法,在来分享,如果有发现内容有任何错误,也欢迎留言给予建议。