kotlin中使用了 object
、companion object
關(guān)鍵字用來表示java中的靜態(tài)成員(類似靜態(tài)成員)。
在實(shí)現(xiàn)雙重校驗(yàn)鎖單例模式時(shí),我嘗試了object
和companion object
,在網(wǎng)上想查詢這兩者的單例有什么區(qū)別,但好像也沒查到什么資料。
先貼單例代碼:
/**
* kotlin雙重校驗(yàn)鎖單例模式
*/
object Singleton {
@Volatile
private var instance: ObjectExpression? = null
fun getInstance() = instance ?: synchronized(this) {
instance ?: ObjectExpression().apply {
instance = this
}
}
}
class ObjectExpression {
companion object {
@Volatile
private var instance: ObjectExpression? = null
fun getInstance() = instance ?: synchronized(this) {
instance ?: ObjectExpression().apply {
instance = this
}
}
}
}
- 前者是在
object
中聲明ObjectExpression
的單例 - 后者是在
ObjectExpression
的companion object
聲明的單例
反編譯為java看看兩者區(qū)別:
public final class Singleton {
private static volatile ObjectExpression instance;
public static final Singleton INSTANCE;
@NotNull
public final ObjectExpression getInstance() {
ObjectExpression var10000 = instance;
if (instance == null) {
synchronized(this){}
ObjectExpression var3;
try {
var10000 = instance;
if (instance == null) {
ObjectExpression var2 = new ObjectExpression();
instance = var2;
var10000 = var2;
}
var3 = var10000;
} finally {
;
}
var10000 = var3;
}
return var10000;
}
//通過靜態(tài)代碼塊生成INSTANCE實(shí)例
static {
Singleton var0 = new Singleton();
INSTANCE = var0;
}
}
public final class ObjectExpression {
private static volatile ObjectExpression instance;
//相應(yīng)類加載時(shí)生成Companion對(duì)象
public static final ObjectExpression.Companion Companion = new ObjectExpression.Companion((DefaultConstructorMarker)null);
public static final class Companion {
@NotNull
public final ObjectExpression getInstance() {
ObjectExpression var10000 = ObjectExpression.instance;
if (var10000 == null) {
synchronized(this){}
ObjectExpression var3;
try {
var10000 = ObjectExpression.instance;
if (var10000 == null) {
ObjectExpression var2 = new ObjectExpression();
ObjectExpression.instance = var2;
var10000 = var2;
}
var3 = var10000;
} finally {
;
}
var10000 = var3;
}
return var10000;
}
private Companion() {
}
// $FF: synthetic method
public Companion(DefaultConstructorMarker $constructor_marker) {
this();
}
}
}
實(shí)際調(diào)用的時(shí)候:
Singleton.getInstance()
ObjectExpression.getInstance()
反編譯為java看看兩者的區(qū)別:
Singleton.INSTANCE.getInstance();
ObjectExpression.Companion.getInstance();
- 前者調(diào)用的是
Singleton
當(dāng)中的INSTANCE
- 后者調(diào)用的是
ObjectExpression
當(dāng)中給的Companion
所以object
內(nèi)部是使用靜態(tài)代碼塊來進(jìn)行INSTANCE
的初始化,而companion object
內(nèi)部是使用靜態(tài)變量來進(jìn)行Companion
的初始化。
不過kotlin官方文檔中有這樣一段話描述object
與companion object
對(duì)象表達(dá)式和對(duì)象聲明之間的語義差異
對(duì)象表達(dá)式和對(duì)象聲明之間有一個(gè)重要的語義差別:
- 對(duì)象表達(dá)式是在使用他們的地方立即執(zhí)行(及初始化)的;
- 對(duì)象聲明是在第一次被訪問到時(shí)延遲初始化的;
- 伴生對(duì)象的初始化是在相應(yīng)的類被加載(解析)時(shí),與 Java 靜態(tài)初始化器的語義相匹配。
經(jīng)過一晚上的研究,終于明白了官方文檔的解釋,同時(shí)也發(fā)現(xiàn)自己基礎(chǔ)知識(shí)的欠缺,路漫漫呀...
那么就來說一說官方文檔所說的第二句話:
- 對(duì)象聲明是在第一次被訪問到時(shí)延遲初始化的;
其實(shí)指的意思是由于object
是一個(gè)獨(dú)立的類(可以通過反編譯java查看),因此object
當(dāng)中的方法第一次訪問時(shí),此時(shí)object
類加載,靜態(tài)代碼塊初始化,INSTANCE
完成創(chuàng)建。
而第三句話:
- 伴生對(duì)象的初始化是在相應(yīng)的類被加載(解析)時(shí),與 Java 靜態(tài)初始化器的語義相匹配。
指的意思是companion object
的外部類加載時(shí),由于companion
是靜態(tài)變量,外部類加載的時(shí)候,會(huì)進(jìn)行初始化,所以等同于外部類的靜態(tài)成員。
而自己知識(shí)的欠缺體現(xiàn)在關(guān)于類加載的時(shí)機(jī)與過程上,貼幾個(gè)問題,思考一下輸出結(jié)果是什么:
public class Singleton {
private static Singleton instance = new Singleton();
public static int count1;
public static int count2 = 0;
private Singleton(){
count1 ++;
count2 ++;
}
public static Singleton getInstance(){
return instance;
}
}
public class Test {
public static void main(String[] args){
Singleton singleton = Singleton.getInstance();
System.out.println("count1 = " + Singleton.count1);
System.out.println("count2 = " + Singleton.count2);
}
}
count1 = 1
count2 = 1 ?
錯(cuò)×
正確答案是:
count1 = 1
count2 = 0
其實(shí)問題就是牽涉到類的加載與過程,虛擬機(jī)定義了以下六種情況,如果類未被初始化,則會(huì)進(jìn)行初始化:
- 創(chuàng)建類的實(shí)例
- 訪問類的靜態(tài)變量(除常量【被final修辭的靜態(tài)變量】原因:常量一種特殊的變量,因?yàn)榫幾g器把他們當(dāng)作值(value)而不是域(field)來對(duì)待。如果你的代碼中用到了常變量(constant variable),編譯器并不會(huì)生成字節(jié)碼來從對(duì)象中載入域的值,而是直接把這個(gè)值插入到字節(jié)碼中。這是一種很有用的優(yōu)化,但是如果你需要改變final域的值那么每一塊用到那個(gè)域的代碼都需要重新編譯。
- 訪問類的靜態(tài)方法
- 反射如(Class.forName("my.xyz.Test"))
- 當(dāng)初始化一個(gè)類時(shí),發(fā)現(xiàn)其父類還未初始化,則先出發(fā)父類的初始化
- 虛擬機(jī)啟動(dòng)時(shí),定義了main()方法的那個(gè)類先初始化
那么我們來分析以下上述代碼的執(zhí)行情況:
-
main()
方法Test
類初始化 -
main()
方法第一句:訪問Singleton
的getInstance()
靜態(tài)方法Singleton
類初始化,此時(shí)按照代碼執(zhí)行順序進(jìn)行靜態(tài)成員的初始化默認(rèn)值-
instance
= null -
count1
= 0 -
count2
= 0
-
-
按照代碼執(zhí)行順序為類的靜態(tài)成員賦值:
-
private static Singleton instance = new Singleton();
instance
調(diào)用Singleton
的構(gòu)造方法,調(diào)用構(gòu)造方法后 count1 = 1,count2 = 1 -
public static int count1;
count1
沒有進(jìn)行賦值操作,所以count1 = 1 -
public static int count2 = 0;
count2
進(jìn)行賦值操作,所以count2 = 0
-
-
main()
方法第二句:訪問Singleton
的count1
變量,由于count1
沒有賦初始值,所以count1 = 1 -
main()
方法第三局:訪問Singleton
的count2
變量,由于count2
賦了初始值 0,所以count2 = 0
所以如果我們把Singleton
代碼執(zhí)行順序變化一下:
public class Singleton {
public static int count1;
public static int count2 = 0;
private static Singleton instance = new Singleton();
private Singleton() {
count1++;
count2++;
}
public static Singleton getInstance() {
return instance;
}
}
那么此時(shí)輸出結(jié)果就為:
count1 = 1
count2 = 1
如果改為如下代碼,那么運(yùn)行情況又是怎樣:
public class Singleton {
Singleton(){
System.out.println("Singleton construct");
}
static {
System.out.println("Singleton static block");
}
public static final int COUNT = 1;
}
public class Test {
public static void main(String[] args) {
System.out.println("count = " + Singleton.COUNT);
}
}
運(yùn)行結(jié)果為:
count = 1
由于常量在編譯階段會(huì)存入相應(yīng)類的常量池當(dāng)中,所以在實(shí)際調(diào)用中Singleton.COUNT
并沒有直接引用到Singleton
類,因此不會(huì)進(jìn)行Singleton
類的初始化,所以輸出結(jié)果為 count = 1