单例声明
kotlin中,声明一个单例的语法很简单:1
object obj
我们使用object
关键字替代class
关键字就可以声明一个单例对象object
一样可以继承其他类,或者实现其他接口:1
2
3interface IObj
abstract class AbstractObj
object obj : AbstractObj(),IObj
在这里,我们让obj
这个单例继承了AbstractObj
,并且实现了IObj
接口
声明一个单例对象,和声明一个class
很类似
但是,object
声明的单例对象不能声明构造函数,因为单例对象只有一个实例,无需我们手动将它创建出来,因此自然不需要构造函数。
如果需要对单例对象做初始化操作,可以在
init
初始化块内进行
那么object
是什么时候被创建的呢?
官方文档的解释是,
object
是lazy-init
,即在第一次使用时被创造出来的
object
单例基本的使用就像上面这样了,基本的用法参照官方的文档说明就好了
实现
在java中,我们要使用一个单例模式时,一般使用双重检查锁:
1 | public class Obj { |
而相同的功能,在kotlin只要object Obj
就搞定了,这样的黑魔法是怎么实现的呢?
为了探究一二,我们先来看看编译之后的字节码:
1 | //源代码: |
1 | //对应的字节码: |
从上面的字节码中,我们可以看到,声明一个object
,实际上就是创建了一个class
,在类静态初始化时,会创建一个该类的实例,保存到其静态域INSTANCE
中
进而可以猜想,源码中对单例的引用会被编译器替换为对INSTANCE
这个静态域的引用
为了验证我们的分析,现在来看看使用单例时,对应的字节码1
2
3
4源码:
fun main(args:Array<String>){
Obj is Any
}
1 | 对应的字节码: |
可以看到,我们在源码中直接使用object name
访问单例对象,而编译器帮我们做了翻译,实际使用的是内部的静态域INSTANCE
而且,从对上面Obj
这个生成的类的分析,我们可以发现,object
在java中的对应的实现是类型这样的:
1 | public class Obj { |
这是最简单的单例实现方法,在类加载器加载class后执行静态初始化时就创建单例对象。
而object
单例初始化的时机,准确来说,应该是这个类被加载时,静态初始化的时候。
做个小实验:
1 | object Obj{ |
1 | fun main(args:Array<String>){ |
控制台输出:object init…
可见,当我们加载这个类的时候,单例就被创建了。
而单例名就是类名。
那么,object
真的就是单例吗?
一般情况下是的,因为字节码中<init>
方法被声明为private
,虽然不太准确但是我们可以认为对应了类的一个private
的无参构造函数,这就使得我们无法创建出一个新的对象出来。
但是,我们完全可以使用反射机制,从一个private
的构造函数中创建一个对象出来:1
2
3
4
5
6
7
8
9
10
11
12
13
14fun 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的单例是不安全的吗?这到未必
我们在原先的基础上,加上几个属性声明:1
2
3
4
5
6
7object Obj{
var name="name"
var age="10"
init{
println("object init...")
}
}
观察对应的字节码:1
2
3
4
5
6
7
8public 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
,但是这个class
在kotlin
中是透明的,无法直接访问,比如Obj.INSTANCE
在kotlin中是不允许的,只能通过Obj
来引用这个单例object name
本质上是类名,只是编译器在编译时自动将object name
换成了object
的INSTANCE
object
更像是语法糖