重学 Kotlin —— object,史上最 “快” 单例 ?(2)

  • 对象表达式 ,一般用来代替 Java 的匿名内部类

下面就逐个来看看这三种用法的本质。

对象声明

object 的语义是这样的: 定义一个类并创建一个实例 。不管是对象声明,还是下面会说到的另外两种用法,都是遵循这一语义的。

作为对象声明,它可以直接用来实现单例模式:

object Singleton{ fun xxx(){} }

话不多说,直接 Decompile 看 Java 代码:

public final class Singleton { public static final Singleton INSTANCE; public final void xxx() { } private Singleton() { } static { Singleton var0 = new Singleton(); INSTANCE = var0; } }

从 Java 代码中可以看出来,显然这是一个单例模式。

  • 私有构造函数
  • 通过静态字段对外提供实例
  • 静态代码块中直接初始化,线程安全

这里插播一个问题,static 代码块在何时执行?

首先类加载阶段可以分为加载验证准备解析初始化使用卸载 七个步骤 。static 代码块就是在 初始化 阶段执行的。那么,哪些场景会触发类的初始化呢?有如下几种场景:

  • 通过 new 实例化对象
  • 读写一个类的静态字段
  • 调用一个类的静态方法
  • 对类进行反射调用

按照上面反编译出来的 Java 代码,获得单例对象的方法是 Singleton.INSTANCE ,即调用 Singleon 类的静态字段 INSTANCE,就会触发类的初始化阶段,也就触发了 static 代码块的执行,从而完成了单例对象的实例化。同时,由于类加载过程天生线程安全,所以 Kotlin 的 object 单例活脱脱的就是一个线程安全的懒汉式单例(访问时初始化)。

此外,object 声明的单例类和普通类一样,可以实现接口,继承类,也可以包含属性,方法。但是它不能由开发者手动声明构造函数,从反编译出来的 Java 代码可以看到,它只有一个 private 构造函数。

所以,这对实际的业务场景是有一定限制的。对于需要携带参数的单例类,object 就有点力不从心了。当然也不难解决,模仿 Java 的写法就行了,这里以 DCL 模式为例。

class Singleton private constructor(private val param: Int) { companion object { @Volatile private var instance: Singleton? = null fun getInstance(property: Int) = instance ?: synchronized(this) { instance ?: Singleton(property).also { instance = it } } } }

说到这,你应该了解了 object 实现单例模式的本质。下面来看看 伴生对象

伴生对象

你可以回想一下,你在 Kotlin 中使用过 static 关键字吗?答案肯定是没有。通常我们可以在顶层文件中直接定义常量和顶层函数,但有的时候我们的确需要在类中定义静态常量或函数,这样显得更加直观。这就是 伴生对象 的应用场景。

伴生对象,顾名思义,就是伴随着类而存在的对象,在类加载的时候初始化。

class User(val male: Int){ companion object { val MALE = 0 fun isMale(male:Int) = male == MALE } }

这样就可以像调用 static 一样调用伴生对象中的属性和函数,而无需创造类实例。

User.MALE User.isMale(1)

还是直接看 Java 代码。

public final class User { private final int male; private static final int MALE = 0; public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null); public final int getMale() { return this.male; } public User(int male) { this.male = male; } public static final class Companion { public final int getMALE() { return User.MALE;public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null); } public final boolean isMale(int male) { return male == ((User.Companion)this).getMALE(); } private Companion() { } // $FF: synthetic method public Companion(DefaultConstructorMarker $constructor_marker) { this(); } } }

编译器为我们生成了一个叫做 Companion 的静态内部类,注意它的 getMale()isMale() 方法并不是静态方法,所以实际去访问的时候还是需要一个 Companion 实例的。这里实例就是 User 类中定义的静态成员变量 Companion

public static final User.Companion Companion = new User.Companion((DefaultConstructorMarker)null);

看到静态字段,又该想到在类加载的时候初始化的了。那么,哪个操作触发了类加载呢?我们来反编译一下 User.MALE 的 Java 代码。

User.Companion.getMALE();

所以也是访问时时会初始化伴生对象。再回想一下前面说过的,

object 其实我们可以把它理解成 定义一个类并创建一个实例

伴生对象仍旧符合这一语义。

在 Java 中如何调用伴生对象呢?User.Companion.isMale(1) 即可。另外,我们可以给伴生对象命名,如下所示:

companion object X { ... }

那么,编译器生成的类就不是 Companion 了,而是 X 。在 Java 中就可以用 User.X.isMale(1) 了。

了解了伴生对象的本质之后,再来说两个它的其他用法。

创建静态工厂方法

interface Car { val brand: String companion object { operator fun invoke(type: CarType): Car { return when (type) { CarType.AUDI -> Audi() CarType.BMW -> BMW() } } } }

这里重载了 invoke() 方法,调用时直接 Car(CarType.BMW) 即可。你可以试着用 Java 代码实现上面的逻辑,对比一下。

伴生对象扩展方法

伴生对象也是支持扩展方法的。还是以上面的 Car 为例,定义一个根据汽车品牌获取汽车类型的扩展方法。

fun Car.Companion.getCarType(brand:String) :CarType { ...... }

虽然是在 Car.Companion 上定义的扩展函数,但实际上相当于给 Car 增加了一个静态方法,使用方式如下:

Car.getCarType("BMW")

对象表达式

对象表达式最经典的用法就是用来 代替 Java 的匿名内部类 。例如常见的点击事件:

xxx.setOnClickListener(object : View.OnClickListener{ override fun onClick(v: View) { } })

这和 Java 的匿名内部类是等价的。只不过像上面的单方法接口,我们很少用 object 写,而是用 lambda 代替,显得更加简洁。

学习分享,共勉

Android高级架构师进阶之路

题外话,我在阿里工作多年,深知技术改革和创新的方向,Android开发以其美观、快速、高效、开放等优势迅速俘获人心,但很多Android兴趣爱好者所需的进阶学习资料确实不太系统,完整。今天我把我搜集和整理的这份学习资料分享给有需要的人

  • Android进阶知识体系学习脑图

  • Android进阶高级工程师学习全套手册

  • 对标Android阿里P7,年薪50w+学习视频

  • 大厂内部Android高频面试题,以及面试经历


《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
[外链图片转存中…(img-4pYSFwRr-1715420136176)]

  • 大厂内部Android高频面试题,以及面试经历

[外链图片转存中…(img-u6nSTp4E-1715420136176)]
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值