kotlin中的object更像是语法糖

kotlin中,声明一个单例的语法很简单:

object obj

我们使用object关键字替代class关键字就可以声明一个单例对象
object一样可以继承其他类,或者实现其他接口:

interface IObj
abstract class AbstractObj
object obj : AbstractObj(),IObj

在这里,我们让obj这个单例继承了AbstractObj,并且实现了IObj接口
声明一个单例对象,和声明一个class很类似
但是,object声明的单例对象不能声明构造函数,因为单例对象只有一个实例,无需我们手动将它创建出来,因此自然不需要构造函数。

如果需要对单例对象做初始化操作,可以在init初始化块内进行

那么object是什么时候被创建的呢?

官方文档的解释是,objectlazy-init,即在第一次使用时被创造出来的

object单例基本的使用就像上面这样了,基本的用法参照官方的文档说明就好了,可以说官方的文档是学习Kotlin最好的学习资料,强烈建议人手一本

安利一下,这是国内最完整的官方文档的翻译,而且和官方保持同步:
https://www.kotlincn.net/docs/reference/
或者:
https://www.gitbook.com/book/hltj/kotlin-reference-chinese/details
这两个网站都提供了电子版下载,如果更喜欢看中文教程的话,可以看一下哦~~~

那么现在开始进入本文的正文
在java中,我们要使用一个单例模式时,最基本的方法是:

public class Obj {
      private Obj(){}
      private static Obj INSTANCE;
      public static Obj getObj(){
          if(INSTANCE==null)
              INSTANCE=new Obj();
          return INSTANCE;
      }
}

而相同的功能,在kotlin只要object Obj就搞定了,这样的黑魔法是怎么实现的呢?
为了探究一二,我们先来看看编译之后的字节码:

源代码:
object Obj{
    init{
        println("object init...")
    }
}
对应的字节码:
public final class Obj {   //可以看到生成了一个class,而类名就是object name
  // access flags 0x2
  private <init>()V     //注意看,<init>的可见性是`private`
   L0
    LINENUMBER 8 L0
    ALOAD 0  //将局部变量表slot 0处的引用入栈,即this引用
    INVOKESPECIAL java/lang/Object.<init> ()V   //调用父类的<init>
    ALOAD 0  //和上面一样,将局部变量表slot 0处的引用入栈,即this引用
    CHECKCAST Obj    //类型转换
    PUTSTATIC Obj.INSTANCE : LObj;     //保存this引用到`INSTANCE`这个静态域
   L1
    LINENUMBER 10 L1
    LDC "object init..."    //从常量池将字符串引用送至栈顶
    ASTORE 1    //将栈顶的引用保存到局部遍历表第一个slot处
   L2
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream;    //获取out实例
    ALOAD 1    //从局部变量表第一个slot处加载引用到栈顶
    INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/Object;)V   //输出
   L3
   L4
   L5
    RETURN  //返回
   L6
    LOCALVARIABLE this LObj; L0 L6 0
    MAXSTACK = 2    //操作数栈深度为2
    MAXLOCALS = 2    //局部变量表为2个slot
  // access flags 0x19
  public final static LObj; INSTANCE    //**静态域**,类型为Obj
  // access flags 0x8
  static <clinit>()V    //静态初始化块,类初始化时执行
   L0
    LINENUMBER 8 L0
    NEW Obj     //创建一个Obj实例,引用保存在栈顶
    INVOKESPECIAL Obj.<init> ()V   //调用其<init>,初始化对象,此时会把栈顶引用作为this引用传入
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 0
}

从上面的字节码中,我们可以看到,声明一个object,实际上就是创建了一个class,在类静态初始化时,会创建一个该类的实例,保存到其静态域INSTANCE
进而可以猜想,源码中对单例的引用会被编译器替换为对INSTANCE这个静态域的引用
为了验证我们的分析,现在来看看使用单例时,对应的字节码

源码:
fun main(args:Array<String>){
    Obj is Any
}
对应的字节码:
public final static main([Ljava/lang/String;)V
    @Lorg/jetbrains/annotations/NotNull;() // invisible, parameter 0
   L0
    ALOAD 0
    LDC "args"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
   L1
    LINENUMBER 5 L1
    GETSTATIC Obj.INSTANCE : LObj;    //获取Obj的静态域`INSTANCE`
    INSTANCEOF java/lang/Object        //判断是否是`Object`类型
    POP
   L2
    LINENUMBER 6 L2
    RETURN
   L3
    LOCALVARIABLE args [Ljava/lang/String; L0 L3 0
    MAXSTACK = 2
    MAXLOCALS = 1

可以看到,我们在源码中直接使用object name访问单例对象,而编译器帮我们做了翻译,实际使用的是内部的静态域INSTANCE
而且,从对上面Obj这个生成的类的分析,我们可以发现,object在java中的对应的实现是类型这样的:

public class  Obj {
    private Obj(){}
    private static Obj INSTANCE=null;
    static {
        INSTANCE=new Obj();
    }
}

object单例初始化的时机,准确来说,应该是这个类被加载时,静态初始化的时候。
做个小实验:

object Obj{
    init{
        println("object init...")
    }
}
fun main(args:Array<String>){
         Class.forName("Obj")
    }

控制台输出:object init...

可见,当我们加载这个类的时候,单例就被创建了。
而单例名就是类名。

那么,object真的就是单例吗?
一般情况下是的,因为字节码中<init>方法被声明为private,虽然不太准确但是我们可以认为对应了类的一个private的无参构造函数,这就使得我们无法创建出一个新的对象出来。
但是,我们完全可以使用反射机制,从一个private的构造函数中创建一个对象出来:

fun main(args:Array<String>){
    println(Obj)
    var clazz=Class.forName("Obj")
    var constrouctor=clazz.getDeclaredConstructor()
    constrouctor.setAccessible(true)
    var instance=constrouctor.newInstance()
    constrouctor.setAccessible(false)
    println(instance)
}
输出:
object init...
Obj@511d50c0
object init...
Obj@60e53b93

可见,两次输出的对象引用是不一样的

那么,这就说明kotlin的单例是不安全的吗?这到未必
我们在原先的基础上,加上几个属性声明:

object Obj{
    var name="name"
    var age="10"
    init{
        println("object init...")
    }
}

观察对应的字节码:

public final class Obj {
  private static Ljava/lang/String; name
   ...
  private static Ljava/lang/String; age
  ....
  public final static LObj; INSTANCE
  ...  
}

可以看到,这些属性的field都被声明为static了,尽管可以通过反射手段创建多个object的实例,但是它们的状态都是共享的
总结:

  • object实际上还是生成一个class,但是这个classkotlin中是透明的,无法直接访问,比如Obj.INSTANCE在kotlin中是不允许的,只能通过Obj来引用这个单例
  • object name本质上是类名,只是编译器在编译时自动将object name换成了 objectINSTANCE
  • kotlin更像是对java的包装
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值