ChinaMobileLab / java-vs-kotlin

Java vs Kotlin demo
1 stars 4 forks source link

object声明singleton #1

Closed two8g closed 7 years ago

two8g commented 7 years ago

@Tony---Zhang 我一开始,对kotlin的object声明的具体实现存在怀疑,于是我编译了SingletonKotlin.kt源码,并使用jd反编译class,这样来看,我发现obejct声明的单例是简单的饿汉式. 反编译结果如下:

public final class SingletonImpl
{
  public static final SingletonImpl INSTANCE;

  static
  {
    new SingletonImpl();
  }

  private SingletonImpl()
  {
    INSTANCE = (SingletonImpl)this;
  }
}

但我还是保持怀疑. 查看了kotlin的相关文档和其它人的博客.没能解决我的困惑.

和我类似的同学的笔记(和我反编译结果一样): http://shinelw.com/2017/03/17/kotlin-apply-in-coding/#3-单例模式

我差点信了.

kotlin文档: https://kotlinlang.org/docs/reference/object-declarations.html#semantic-difference-between-object-expressions-and-declarations

object declarations are initialized lazily, when accessed for the first time

文档说的obejct声明是懒加载的, 为什么通过编译代码的结果却相反? 如何验证文档所说的观点?

Tony---Zhang commented 7 years ago

@two8g 你是对的。 object对应的java代码就是像你说的那样。

http://stackoverflow.com/questions/35587652/kotlin-thread-save-native-lazy-singleton-with-parameter

这里也有一篇介绍的文章,如果你想要比较好的实现,可以使用companionlazy()的delegate配合来使用

zhywang commented 7 years ago

@two8g

Kotlin 的文档是正确的,可以用一段简单的代码验证

object SingletonImpl {
    init {
        println("object is inited")
        Thread.sleep(100)
    }
}

object Main {
    @JvmStatic
    fun main(args: Array<String>) {
        readLine()
        Thread {
            print(SingletonImpl)
        }.run()
        Thread {
            print(SingletonImpl)
        }.run()
    }
}

简化来说,kotlin的object实现原理类似于https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom , 不同的是,由于kotlin语法的特点,SingletonImpl 必须作为一个对象,而不是类名来使用,所以底层的SingletonImpl类事实上承担了Holder的职责。

two8g commented 7 years ago

@zhywang

所以底层的SingletonImpl类事实上承担了Holder的职责。

How ? 我花了很长时间都没有查到相关资料。如果你看来后面我的思考后,认为我暂时无法理解这个底层实现,那请提示我应该学习什么。

我通过javap以及IDEA的Show Bytecode也没有发现任何线索。

Kotlin写法:

object SingletonImpl

Java非lazy写法

public final class SingletonJava {
    private SingletonJava() {
        INSTANCE = (SingletonJava) this;
    }

    public static SingletonJava INSTANCE;

    static {
        new SingletonJava();
    }
}

另外,java源码与之前我反编译的kotlin的代码少了final,否则,编译不通过。另外后面的kotlin的字节码中也确实存在final,我不知所以然。如果你知道,请告诉我。

编译上述两个源码,然后使用javap查看的结果如下:

SingletonImpl

public final class SingletonImpl
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
   #1 = Utf8               SingletonImpl
   #2 = Class              #1             // SingletonImpl
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = NameAndType        #5:#6          // "<init>":()V
   #8 = Methodref          #4.#7          // java/lang/Object."<init>":()V
   #9 = Utf8               INSTANCE
  #10 = Utf8               LSingletonImpl;
  #11 = NameAndType        #9:#10         // INSTANCE:LSingletonImpl;
  #12 = Fieldref           #2.#11         // SingletonImpl.INSTANCE:LSingletonImpl;
  #13 = Utf8               this
  #14 = Utf8               <clinit>
  #15 = Utf8               Lkotlin/Metadata;
  #16 = Utf8               mv
  #17 = Integer            1
  #18 = Integer            5
  #19 = Utf8               bv
  #20 = Integer            0
  #21 = Utf8               k
  #22 = Utf8               d1
  #23 = Utf8                \n\n \nÆ 20B¢¨
  #24 = Utf8               d2
  #25 = Utf8
  #26 = Utf8               jdk8-src
  #27 = Methodref          #2.#7          // SingletonImpl."<init>":()V
  #28 = Utf8               SingletonImpl.kt
  #29 = Utf8               Code
  #30 = Utf8               LocalVariableTable
  #31 = Utf8               LineNumberTable
  #32 = Utf8               SourceFile
  #33 = Utf8               RuntimeVisibleAnnotations
{
  public static final SingletonImpl INSTANCE;
    descriptor: LSingletonImpl;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: new           #2                  // class SingletonImpl
         3: invokespecial #27                 // Method "<init>":()V
         6: return
      LineNumberTable:
        line 1: 0
}
SourceFile: "SingletonImpl.kt"
RuntimeVisibleAnnotations:
  0: #15(#16=[I#17,I#17,I#18],#19=[I#17,I#20,I#17],#21=I#17,#22=[s#23],#24=[s#10,s#25,s#6,s#26])

SingletonJava

public final class SingletonJava
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#17         // java/lang/Object."<init>":()V
   #2 = Fieldref           #3.#18         // SingletonJava.INSTANCE:LSingletonJava;
   #3 = Class              #19            // SingletonJava
   #4 = Methodref          #3.#17         // SingletonJava."<init>":()V
   #5 = Class              #20            // java/lang/Object
   #6 = Utf8               INSTANCE
   #7 = Utf8               LSingletonJava;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               LocalVariableTable
  #13 = Utf8               this
  #14 = Utf8               <clinit>
  #15 = Utf8               SourceFile
  #16 = Utf8               SingletonJava.java
  #17 = NameAndType        #8:#9          // "<init>":()V
  #18 = NameAndType        #6:#7          // INSTANCE:LSingletonJava;
  #19 = Utf8               SingletonJava
  #20 = Utf8               java/lang/Object
{
  public static SingletonJava INSTANCE;
    descriptor: LSingletonJava;
    flags: ACC_PUBLIC, ACC_STATIC

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=2, locals=0, args_size=0
         0: new           #3                  // class SingletonJava
         3: dup
         4: invokespecial #4                  // Method "<init>":()V
         7: pop
         8: return
      LineNumberTable:
        line 7: 0
        line 8: 8
}
SourceFile: "SingletonJava.java"

对应上面的kotlin结果,我发现两个疑问的地方,一是为什么有乱码?而是Metadata注解对讨论的问题是否重要,它是否是你所说的“Holder”?

另外通过IDEA的Show Bytecode的结果如下:

// ================SingletonImpl.class =================
// class version 50.0 (50)
// access flags 0x31
public final class SingletonImpl {

  // access flags 0x2
  private <init>()V
   L0
    LINENUMBER 1 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
    ALOAD 0
    CHECKCAST SingletonImpl
    PUTSTATIC SingletonImpl.INSTANCE : LSingletonImpl;
    RETURN
   L1
    LOCALVARIABLE this LSingletonImpl; L0 L1 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x19
  public final static LSingletonImpl; INSTANCE

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 1 L0
    NEW SingletonImpl
    INVOKESPECIAL SingletonImpl.<init> ()V
    RETURN
    MAXSTACK = 1
    MAXLOCALS = 0

  @Lkotlin/Metadata;(mv={1, 1, 5}, bv={1, 0, 1}, k=1, d1={"\u0000\u000c\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0002\u0008\u0002\u0008\u00c6\u0002\u0018\u00002\u00020\u0001B\u0007\u0008\u0002\u00a2\u0006\u0002\u0010\u0002\u00a8\u0006\u0003"}, d2={"LSingletonImpl;", "", "()V", "test sources for module jdk8-src"})
  // compiled from: SingletonImpl.kt
}
// class version 52.0 (52)
// access flags 0x31
public final class SingletonJava {

  // compiled from: SingletonJava.java

  // access flags 0x9
  public static LSingletonJava; INSTANCE

  // access flags 0x2
  private <init>()V
   L0
    LINENUMBER 2 L0
    ALOAD 0
    INVOKESPECIAL java/lang/Object.<init> ()V
   L1
    LINENUMBER 3 L1
    ALOAD 0
    PUTSTATIC SingletonJava.INSTANCE : LSingletonJava;
   L2
    LINENUMBER 4 L2
    RETURN
   L3
    LOCALVARIABLE this LSingletonJava; L0 L3 0
    MAXSTACK = 1
    MAXLOCALS = 1

  // access flags 0x8
  static <clinit>()V
   L0
    LINENUMBER 7 L0
    NEW SingletonJava
    DUP
    INVOKESPECIAL SingletonJava.<init> ()V
    POP
   L1
    LINENUMBER 8 L1
    RETURN
    MAXSTACK = 2
    MAXLOCALS = 0
}

这里,除了Metadata注解的疑问外,还有就是二者在static <clinit>()V处的DUP/POP区别是不是验证了你所说的“SingletonImpl类事实上承担了Holder的职责”。意思是,Kotlin写法初始化的是Holder中的SingletonImpl实例。

two8g commented 7 years ago

@Tony---Zhang 阅读了你给的连接,我认识到object是lazy的。但是我无法证明它。而zhywang的代码说明了这点,但是我还是存在上面的疑问。我会记住object是使用Initialization-on-demand holder实现的。

Tony---Zhang commented 7 years ago

@two8g 工程实践中,一般我们会用依赖注入来解决这些问 Kotlin目前对Dagger2的支持比较好~所以会配合Dagger2来使用Kotlin。

至于说学什么,Kotlin是一些语法特性,可以提高我们的开发效率。

有以下两个地方:

正如我们在Open Day上所说, 语法糖不能解决你编程思路的问题,只能帮助你尽早发现一些问题。 你非要把UI跟数据代码写在一起,功能极度耦合也是没有办法的。

two8g commented 7 years ago

@Tony---Zhang 抱歉,我不是在讨论Kotlin In Android, 而是想明白Kotlin的object是怎么实现的,由此加深对Kotlin了解。

Tony---Zhang commented 7 years ago

@two8g

https://github.com/JetBrains/kotlin

参考这里吧,代码量很庞大,我一时也没找到object的语法的定义。 这比你反编译会靠谱点~

zhywang commented 7 years ago

@two8g 不好意思,最近没有看github notification,今天才看到。

https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom 的原理是通过JVM加载类时的特性来做延迟初始化的,只有LazyHolder类被用到后才初始化它的静态字段,这个静态字段就是Something的单例。

kotlin里的object,没有办法在不用对象引用的情况下提前引用到Something类,即当你在代码中引用它时,你拿到的就是一个对象,这个对象只有在你用到的时候才会被初始化,也就是延迟加载。

关于object的实现,如果你从Class文件反编译成Java的角度来分析,它就是一个最简单形式的单例实现,你原post的结果完全正确。

two8g commented 7 years ago

谢谢二位的解惑,我现在清楚object的延迟初始化含义了。