Closed two8g closed 7 years ago
@two8g 你是对的。 object对应的java代码就是像你说的那样。
http://stackoverflow.com/questions/35587652/kotlin-thread-save-native-lazy-singleton-with-parameter
这里也有一篇介绍的文章,如果你想要比较好的实现,可以使用companion
跟lazy()
的delegate配合来使用
@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的职责。
@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实例。
@Tony---Zhang 阅读了你给的连接,我认识到object是lazy的。但是我无法证明它。而zhywang的代码说明了这点,但是我还是存在上面的疑问。我会记住object是使用Initialization-on-demand holder实现的。
@two8g 工程实践中,一般我们会用依赖注入来解决这些问 Kotlin目前对Dagger2的支持比较好~所以会配合Dagger2来使用Kotlin。
至于说学什么,Kotlin是一些语法特性,可以提高我们的开发效率。
有以下两个地方:
正如我们在Open Day上所说, 语法糖不能解决你编程思路的问题,只能帮助你尽早发现一些问题。 你非要把UI跟数据代码写在一起,功能极度耦合也是没有办法的。
@Tony---Zhang 抱歉,我不是在讨论Kotlin In Android, 而是想明白Kotlin的object是怎么实现的,由此加深对Kotlin了解。
@two8g 不好意思,最近没有看github notification,今天才看到。
https://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom 的原理是通过JVM加载类时的特性来做延迟初始化的,只有LazyHolder类被用到后才初始化它的静态字段,这个静态字段就是Something的单例。
kotlin里的object,没有办法在不用对象引用的情况下提前引用到Something类,即当你在代码中引用它时,你拿到的就是一个对象,这个对象只有在你用到的时候才会被初始化,也就是延迟加载。
关于object的实现,如果你从Class文件反编译成Java的角度来分析,它就是一个最简单形式的单例实现,你原post的结果完全正确。
谢谢二位的解惑,我现在清楚object的延迟初始化含义了。
@Tony---Zhang 我一开始,对kotlin的object声明的具体实现存在怀疑,于是我编译了SingletonKotlin.kt源码,并使用jd反编译class,这样来看,我发现obejct声明的单例是简单的饿汉式. 反编译结果如下:
但我还是保持怀疑. 查看了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
文档说的obejct声明是懒加载的, 为什么通过编译代码的结果却相反? 如何验证文档所说的观点?