object关键字

object关键字,第1张

object关键字 object关键字
//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 了。
*/

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5707972.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存