//Kotlin 当中 object 关键字,有三种迥然不同的语义,分别可以定义:匿名内部类;单例模式;伴生对象 // 在 Java 开发当中,这就是典型的匿名内部类的写法,View.onClickListener 是一个接口, //因此我们在创建它的时候,必须实现它内部没有实现的方法。 // Java 和 Kotlin 相同的地方就在于,它们的接口与抽象类,都不能直接创建实例。 //想要创建接口和抽象类的实例,我们必须通过匿名内部类的方式。 // 在 Kotlin 中,匿名内部类还有一个特殊之处,就是我们在使用 object 定义匿名内部类的时候, //其实还可以在继承一个抽象类的同时,来实现多个接口。 interface A { fun funA() } interface B { fun funB() } abstract class Man { abstract fun findMan() } fun main() { // 这个匿名内部类,在继承了Man类的同时,还实现了A、B两个接口 val item = object : Man(), A, B{ override fun funA() { // do something } override fun funB() { // do something } override fun findMan() { // do something } } } //Kotlin 当中,要实现单例模式其实非常简单,我们直接用 object 修饰类即可: object UserManager { fun login() {} } //Java 当中 // 当我们使用 object 关键字定义单例类的时候,Kotlin 编译器会将其转换成静态代码块的单例模式。 //因为static{}代码块当中的代码,由虚拟机保证它只会被执行一次,因此,它在保证了线程安全的前提下, //同时也保证我们的 INSTANCE 只会被初始化一次。 //将单例定义到一个类的内部,单例就跟外部类形成了一种嵌套的关系 class Person { object InnerSingleton { fun foo() {} } } //要使用它的话,可以直接这样写: //Person.InnerSingleton.foo() //Java写法 //可以看到,foo() 并不是静态方法,它实际上是通过调用单例 InnerSingleton 的实例上的方法实现的: // Kotlin当中这样调用 //Person.InnerSingleton.foo() // 等价 // ↓ java 当中这样调用 //Person.InnerSingleton.INSTANCE.foo() //如何实现类似 Java 静态方法的代码?———————可以使用“@JvmStatic”这个注解 class Person2 { object InnerSingleton { @JvmStatic fun foo() {} } } //Java写法 // companion object,在 Kotlin 当中就被称作伴生对象,它其实是我们嵌套单例的一种特殊情况。 //也就是,在伴生对象的内部,如果存在“@JvmStatic”修饰的方法或属性,它会被挪到伴生对象外部的类当中, //变成静态成员。 class Person3 { // 改动在这里 // ↓ companion object InnerSingleton { @JvmStatic fun foo() {} } } //Java写法 //object 单例、伴生对象中间的演变关系了:普通的 object 单例,演变出了嵌套的单例;嵌套的单例,演变出了伴生对象。 //也可以换个说法:嵌套单例,是 object 单例的一种特殊情况;伴生对象,是嵌套单例的一种特殊情况。 // 前面我们已经使用 object 关键字实现了最简单的单例模式,这种方式的缺点是不支持懒加载、 //不支持“getInstance() 传递参数”。而借助 Kotlin 的伴生对象,我们可以实现功能更加全面的单例模式。 // 所谓的工厂模式,就是指当我们想要统一管理一个类的创建时,我们可以将这个类的构造函数声明成 private, //然后用工厂模式来暴露一个统一的方法,以供外部使用。Kotlin 的伴生对象非常符合这样的使用场景: // 私有的构造函数,外部无法调用 // ↓ class User private constructor(name: String) { companion object { @JvmStatic fun create(name: String): User? { // 统一检查,比如敏感词过滤 return User(name) } } } //第一种写法:借助懒加载委托 //针对懒加载的问题,我们在原有的代码基础上做一个非常小的改动就能优化,也就是借助 Kotlin 提供的“委托”语法。 //我们在它内部的属性上使用 by lazy 将其包裹起来,这样我们的单例就能得到一部分的懒加载效果。 object UserManager2 { // 对外暴露的 user val user by lazy { loadUser() } private fun loadUser(): User? { // 从网络或者数据库加载数据 return User.create("tom") } fun login() {} } //UserManager 内部的 user 变量变成了懒加载,只要 user 变量没有被使用过,它就不会触发 loadUser() 的逻辑。 //第二种写法:伴生对象 Double Check class UserManager3 private constructor(name: String) { companion object { @Volatile private var INSTANCE: UserManager3? = null fun getInstance(name: String): UserManager3 = // 第一次判空 //synchronized可以保证方法或代码块在运行时,同一时刻只有一个线程可以进入到临界区(互斥性),同时它还保证了共享变量的内存可见性。 INSTANCE?: synchronized(this) { // 第二次判空 INSTANCE?:UserManager3(name).also { INSTANCE = it } } } } // 使用 //UserManager3.getInstance("Tom") //这种写法,其实是借鉴于 GitHub 上的Google 官方 Demo,它本质上就是 Java 的 Double Check。 //分析下第二种写法的单例。发现它主要由两个部分组成:第一部分是 INSTANCE 实例,第二部分是 getInstance() 函数。 //在面向对象的编程当中,我们主要有两种抽象手段,第一种是类抽象模板,第二种是接口抽象模板。 //类抽象模板 // ① ② // ↓ ↓ abstract class baseSingleton小结{ @Volatile private var instance: T? = null // ③ // ↓ protected abstract fun creator(param: P): T fun getInstance(param: P): T = instance ?: synchronized(this) { // ④ // ↓ instance ?: creator(param).also { instance = it } } } class PersonManager private constructor(name: String) { // ① ② // ↓ ↓ companion object : baseSingleton () { // ③ // ↓ override fun creator(param: String): PersonManager = PersonManager(param) } } //接口模板(这种方式是不被推荐的) interface ISingleton { // ① var instance: T? fun creator(param: P): T fun getInstance(p: P): T = instance ?: synchronized(this) { instance ?: creator(p).also { instance = it } } } //根据注释①的地方,会发现: //instance 无法使用 private 修饰。这是接口特性规定的,而这并不符合单例的规范。正常情况下的单例模式,我们内部的 instance 必须是 private 的,这是为了防止它被外部直接修改。 //instance 无法使用 @Volatile 修饰。这也是受限于接口的特性,这会引发多线程同步的问题。 //除了 ISingleton 接口有这样的问题,我们在实现 ISingleton 接口的类当中,也会有类似的问题。 class Singleton private constructor(name: String) { companion object: ISingleton
{ // ① ② // ↓ ↓ @Volatile override var instance: Singleton? = null override fun creator(param: String): Singleton = Singleton(param) } } //注释①:@Volatile,这个注解虽然可以在实现的时候添加,但实现方可能会忘记,这会导致隐患。 //注释②:我们在实现 instance 的时候,仍然无法使用 private 来修饰。
Kotlin 的匿名内部类和 Java 的类似,只不过它多了一个功能:匿名内部类可以在继承一个抽象类的同时还实现多个接口。
另外,object 的单例和伴生对象,这两种语义从表面上看是没有任何联系的。其实单例与伴生对象之间是存在某种演变关系的。“单例”演变出了“嵌套单例”,而“嵌套单例”演变出了“伴生对象”。
然后,借助 Kotlin 伴生对象这个语法,研究了伴生对象的实战应用,比如可以实现工厂模式、懒加载 + 带参数的单例模式。
尤其是单例模式,一共提出了 Kotlin 当中 5 种单例模式的写法。除了最后一种“接口模板”的方式,是为了学习研究不被推荐使用以外,其他 4 种单例模式都是有一定使用场景的。这 4 种单例之间各有优劣,我们可以在工作中根据实际需求,来选择对应的实现方式:
如果我们的单例占用内存很小,并且对内存不敏感,不需要传参,直接使用 object 定义的单例即可。如果我们的单例占用内存很小,不需要传参,但它内部的属性会触发消耗资源的网络请求和数据库查询,我们可以使用 object 搭配 by lazy 懒加载。如果我们的工程很简单,只有一两个单例场景,同时我们有懒加载需求,并且 getInstance() 需要传参,我们可以直接手写 Double Check。如果我们的工程规模大,对内存敏感,单例场景比较多,那我们就很有必要使用抽象类模板 baseSingleton 了。
*/
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)