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

公众号历史文章不支持修改,掘金分类功能太拉胯,我会在 小专栏 上长期维护 重学 Kotlin 系列文章。web

本文永久更新地址: xiaozhuanlan.com/topic/43198…安全

前言

这里是专栏 重学 Kotlin,灵感来自于 Medium 上 Android Developers 团队的 Kotlin Vocabulary编辑器

做为一名 Kotlin 老铁粉,我可能在博客里不止一次的表达过对 Kotlin 的态度。ide

都 2020 了,做为一名安卓开发者,再不会 Kotlin ,真的说不过去了!函数

介绍 Kotlin 语法的文章不少,那么,在这个系列中,我会写一些什么呢?学习

Kotlin 再强大,也逃脱不了在 JVM 上运行。通过 kotlinc 编译以后,生成的依旧是 .class 文件。flex

因此,学习 Kotlin 的最佳方式其实就是查看字节码。Android Studio 直接提供了插件,按以下方式便可查看:ui

Tools -> Kotlin -> Show Kotlin Bytecodethis

固然,字节码可读性太差,IDE 提供了 Decompile ,将字节码转换成 Java 代码。url

这样,咱们就能够轻松掌握 Kotlin 各类语法的本质。

本系列的每一篇文章都会选择一个关键字或者知识点,剖析本质,帮助你们快速深刻理解 Kotlin 。

下面就进入今天的主角 object

目录

  1. object 有哪些用法?

  2. 对象声明 —— 一个关键字实现单例 ?

  3. 伴生对象 —— static 的代替者 ?

  4. 对象表达式 —— Kotlin 的匿名内部类 ?

  5. 这究竟是哪一种用法 ?

正文

object 的三种用法

Kotlin 的 object 关键字有三种用法:

  • 对象声明 ,通常用来实现单例
  • 伴生对象 ,相似 Java 的 static 关键字,也能够用于工厂方法模式
  • 对象表达式 ,通常用来代替 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 代替,显得更加简洁。

xxx.setOnClickListener { view ->  ...... }
复制代码

当匿名对象须要重写多个方法时,就只能选择对象表达式了。

和 Java 的匿名内部类同样,对象声明中也能够访问外部变量。

对象表达式应该是 object 最朴实无华的使用方式了。

最后

看到这里,你应该已经彻底掌握了 object 关键字的本质。那么,我也要来考考你,仔细看下面的代码:

class MainActivity : AppCompatActivity() {
 val a = 1   object click : View.OnClickListener {  override fun onClick(v: View) {  val b = a + 1  }  } } 复制代码
  • 上面的代码能够正确编译吗?为何?
  • 这里 object 的用法属于哪种?

在评论区留下你的答案吧!

本文使用 mdnice 排版

我是秉心说,关注我,不迷路!

相关文章
相关标签/搜索