8e90d01d3ed011f026f6a1421e2b73f0
object,史上最 “快” 单例 ?

前言

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

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

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

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

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

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

Tools -> Kotlin -> Show Kotlin Bytecode

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

fangfa

这样,我们就可以轻松掌握 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 代码。

```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();
  }
top Created with Sketch.