1. 程式人生 > 其它 >java 許可權不同看到的不同_Kotlin與Java的不同之處

java 許可權不同看到的不同_Kotlin與Java的不同之處

技術標籤:java 許可權不同看到的不同

code小生,一個專注 Android 領域的技術平臺

公眾號回覆 Android 加入我的安卓技術群

作者:小村醫

連結:https://www.jianshu.com/p/f7deb4fe6427

宣告:本文已獲小村醫授權發表,轉發等請聯絡原作者授權

伴生物件

在 Kotlin 中並不沒有 static 這個關鍵字,該如何處理呢?這裡需要用到 Kotlin 的伴生物件來處理。

類內部的物件宣告可以用 companion 關鍵字標記:

classMyClass{
companionobjectFactory{
funcreate():MyClass=MyClass()
}
}

該伴生物件的成員可通過只使用類名作為限定符來呼叫:

valinstance=MyClass.create()

可以省略伴生物件的名稱,在這種情況下將使用名稱 Companion:

classMyClass{
companionobject{}
}

valx=MyClass.Companion

伴生物件的作用

類似於 Java 中使用類訪問靜態成員的語法。因為 Kotlin 取消了 static 關鍵字,所以 Kotlin 引入伴生物件來彌補沒有靜態成員的不足。可見,伴生物件的主要作用就是為其所在的外部類模擬靜態成員。

在 Java 程式碼中呼叫伴生物件

如何在 Java 程式碼中呼叫 Kotlin 的伴生物件呢?

publicstaticvoidmain(String[]args){
System.out.println(MyClass.Factory.create());
System.out.println(MyClass.Companion.create());
}
  • 如果宣告伴生物件有名稱,則使用:

類名.伴生物件名.方法名()
類名.半生物件名.屬性的setter,getter方法
  • 如果宣告伴生物件無名稱,則採用 Companion 關鍵字呼叫:

類名.Companion.方法名()
類名.Companion.屬性的setter,getter方法

@JvmField 和 @JvmStatic 的使用

在上面的例子中,我們知道了可以在 Java 程式碼中呼叫 Kotlin 中伴生物件的成員,類似於 Java 類中的靜態成員。但是看上去和 Java 中的還是略有區別,因為類名和方法名/屬性setter,getter方法名之間多了個伴生物件的名稱或者 Companion 關鍵字。如何使其在呼叫的時候與 Java 中的呼叫看上去一樣呢?

Kotlin 為我們提供了 @JvmField 和 @JvmStatic 兩個註解。@JvmField 使用在屬性上,@JvmStatic 使用在方法上。如:

classTest{
companionobject{
@JvmField
valflag=true

@JvmStatic
funadd(a:Int,b:Int):Int{
returna+b
}
}
}

這樣我們在 Java 程式碼中呼叫的時候就和 Java 類呼叫靜態成員的形式一致了,Kotlin 程式碼呼叫方式不變:

System.out.println(Test.flag);
System.out.println(Test.add(1,2));

const 關鍵字

在伴生物件中,我們可能需要宣告一個常量,目的是等同於 Java 中的靜態常量。有兩種方式,一種是上面所提到的使用 @JvmField 註解,另一種則是使用 const 關鍵字修飾。這兩種宣告方式都等同於 Java 中 static final 所修飾的變數。如下程式碼:

companionobject{
constvalflag=true

@JvmStatic
funadd(a:Int,b:Int):Int{
returna+b
}
}

擴充套件屬性和擴充套件方法

擴充套件函式

Kotlin的擴充套件函式可以讓你作為一個類成員進行呼叫的函式,但是是定義在這個類的外部。這樣可以很方便的擴充套件一個已經存在的類,為它新增額外的方法

下面我們為String新增一個toInt的方法

packagecom.binzi.kotlin

funString?.toInt():Int{
returnjava.lang.Integer.parseInt(this)
}

在這個擴充套件函式中,你可以直接訪問你擴充套件的類的函式和屬性,就像定義在這個類中的方法一樣,但是擴充套件函式並不允許你打破封裝。跟定義在類中方法不同,它不能訪問那些私有的、受保護的方法和屬性。

擴充套件函式的匯入

我們直接在包裡定義擴充套件函式。這樣我們就可以在整個包裡面使用這些擴充套件,如果我們要使用其他包的擴充套件,我們就需要匯入它。匯入擴充套件函式跟匯入類是一樣的方式。

importcom.binzi.kotlin.toInt
或者
importcom.binzi.kotlin.*

有時候,可能你引入的第三方包都對同一個型別進行了相同函式名擴充套件,為了解決衝突問題,你可以使用下面的方式對擴充套件函式進行改名

importcom.binzi.kotlin.toIntastoInteger

擴充套件函式不可覆蓋

擴充套件方法的原理

Kotlin 中類的擴充套件方法並不是在原類的內部進行拓展,通過反編譯為Java程式碼,可以發現,其原理是使用裝飾模式,對源類例項的操作和包裝,其實際相當於我們在 Java中定義的工具類方法,並且該工具類方法是使用呼叫者為第一個引數的,然後在工具方法中操作該呼叫者

如:

funString?.toInt():Int{
returnjava.lang.Integer.parseInt(this)
}

反編譯為對應的Java程式碼:

publicfinalclassExtsKt{
publicstaticfinalinttoInt(@NullableString$this$toInt){
returnInteger.parseInt($this$toInt);
}
}

擴充套件屬性

類的擴充套件屬性原理其實與擴充套件方法是一樣的,只是定義的形式不同,擴充套件屬性必須定義get和set方法

為MutableList擴充套件一個firstElement屬性:

varMutableList.firstElement:Tget(){//擴充套件屬性的get函式returnthis[0]
}set(value){//擴充套件屬性的set函式this[0]=value
}

反編譯後的java程式碼如下:

publicstaticfinalObjectgetFirstElement(@NotNullList$this$firstElement){
Intrinsics.checkParameterIsNotNull($this$firstElement,"$this$firstElement");
return$this$firstElement.get(0);
}

publicstaticfinalvoidsetFirstElement(@NotNullList$this$firstElement,Objectvalue){
Intrinsics.checkParameterIsNotNull($this$firstElement,"$this$firstElement");
$this$firstElement.set(0,value);
}

內部類

kotlin的內部類與java的內部類有點不同java的內部類可以直接訪問外部類的成員,kotlin的內部類不能直接訪問外部類的成員,必須用inner標記之後才能訪問外部類的成員

  • 沒有使用inner標記的內部類

classA{
vara=0
classB{
//B類的內部是不能直接用a變數的
varb=1
}
}

反編譯後的java程式碼

publicfinalclassA{
privateinta;

publicfinalintgetA(){
returnthis.a;
}

publicfinalvoidsetA(intvar1){
this.a=var1;
}

publicstaticfinalclassB{
privateintb=1;

publicfinalintgetB(){
returnthis.b;
}

publicfinalvoidsetB(intvar1){
this.b=var1;
}
}
}
  • 用inner標記的內部類

classA{
vara=0
innerclassB{
//B類的內部可以直接用a變數
varb=a
}
}

反編譯後的java程式碼

publicfinalclassA{
privateinta;

publicfinalintgetA(){
returnthis.a;
}

publicfinalvoidsetA(intvar1){
this.a=var1;
}

publicfinalclassB{
privateintb=A.this.getA();

publicfinalintgetB(){
returnthis.b;
}

publicfinalvoidsetB(intvar1){
this.b=var1;
}
}
}

從上面可以看出,沒有使用inner標記的內部類最後生成的是靜態內部類,而使用inner標記的生成的是非靜態內部類

匿名內部類

匿名內部類主要是針對那些獲取抽象類或者介面物件而來的。最常見的匿名內部類View點選事件:

//java,匿名內部類的寫法
btn.setOnClickListener(newOnClickListener(){
@Override
publicvoidonClick(Viewv){

}
});

上面這個是java匿名內部類的寫法,kotlin沒有new關鍵字,那麼kotlin的匿名內部類該怎麼寫呢?

btn.setOnClickListener(object:View.OnClickListener{
overridefunonClick(v:View?){
print("1111")
}
})

方法的引數是一個匿名內部類,先寫object:,然後寫你的引數型別View.OnClickListener{}

kotlin還有一個寫法lambda 表示式,非常之方便:

btn.setOnClickListener{print("1111")}

資料類

在Java中沒有專門的資料類,常常是通過JavaBean來作為資料類,但在Kotlin中提供了專門的資料類。

  • Java

publicclassPerson{
privateintage;
privateStringname;

publicintgetAge(){
returnage;
}

publicvoidsetAge(intage){
this.age=age;
}

publicStringgetName(){
returnname;
}

publicvoidsetName(Stringname){
this.name=name;
}
}

從上面的例子中可以看到,如果要使用資料類,需要手動寫相應的setter/getter方法(儘管IDE也可以批量生成),但是從程式碼閱讀的角度來說,在屬性較多的情況下,諸多的seeter/getter方法還是不利於程式碼的閱讀和維護。

  • Kotlin
    在Kotlin中,可以通過關鍵字data來生成資料類:

dataclassPerson(valname:String,valage:Int)

即在class關鍵字之前新增data關鍵字即可。編譯器會根據主建構函式中的引數生成相應的資料類。自動生成setter/getter、toString、hashCode等方法

要宣告一個數據類,需要滿足:

  • 主建構函式中至少有一個引數

  • 主建構函式中所有引數需要標記為val或var

  • 資料類不能是抽象、開發、密封和內部的

列舉類

列舉類是一種特殊的類,kotlin可以通過enum class關鍵字定義列舉類。

列舉類可以實現0~N個介面;

  • 列舉類預設繼承於kotlin.Enum類(其他類最終父類都是Any),因此kotlin列舉類不能繼承類;

  • 非抽象列舉類不能用open修飾符修飾,因此非抽象列舉類不能派生子類;

  • 抽象列舉類不能使用abstract關鍵字修飾enum class,抽象方法和抽象屬性需要使用;

  • 列舉類構造器只能使用private修飾符修飾,若不指定,則預設為private;

  • 列舉類所有例項在第一行顯式列出,每個例項之間用逗號隔開,整個宣告以分號結尾;

  • 列舉類是特殊的類,也可以定義屬性、方法、構造器;

  • 列舉類應該設定成不可變類,即屬性值不允許改變,這樣更安全;

  • 列舉屬性設定成只讀屬性後,最好在構造器中為列舉類指定初始值,如果在宣告時為列舉指定初始值,會導致所有列舉值(或者說列舉物件)的該屬性都一樣。

定義列舉類

/**
*定義一個列舉類
*/
enumclassEnumClass(valenumParam:String){
MON("星期一"),TUES("星期二"),WED("星期三");//逗號隔開,分號結尾

/**
*列舉類方法
*/
funenumFun(){
println("列舉值:$this列舉屬性:$enumParam")
}
}

列舉類實現介面

  1. 列舉值分別實現介面的抽象成員

enumclassEnumClass(valenumParam:String):EnumInterface{
MON("星期一"){
overridefuninterfaceFun(){
println(enumParam)
}

overridevalinterfaceParam:String
get()="1"
},
TUES("星期二"){
overridefuninterfaceFun(){
println(enumParam)
}

overridevalinterfaceParam:String
get()="2"
},
WED("星期三"){
overridefuninterfaceFun(){
println(enumParam)
}

overridevalinterfaceParam:String
get()="3"
};//逗號隔開,分號結尾
}
interfaceEnumInterface{
funinterfaceFun()
valinterfaceParam:String
}
  1. 列舉類統一實現介面的抽象成員

enumclassEnumClass(valenumParam:String):EnumInterface{
MON("星期一"),
TUES("星期二"),
WED("星期三");//逗號隔開,分號結尾

overridefuninterfaceFun(){

}

overridevalinterfaceParam:String
get()={

}
}
  1. 分別實現抽象列舉類抽象成員

enumclassAbstractEnumClass{
MON{
overridevalabstractParam:String
get()="星期一"

overridefunabstractFun(){
println(abstractParam)
}
},
TUES{
overridevalabstractParam:String
get()="星期二"

overridefunabstractFun(){
println(abstractParam)
}
},
WED{
overridevalabstractParam:String
get()="星期三"

overridefunabstractFun(){
println(abstractParam)
}
};

abstractvalabstractParam:String
abstractfunabstractFun()
}

委託

委託模式 是軟體設計模式中的一項基本技巧。在委託模式中,有兩個物件參與處理同一個請求,接受請求的物件將請求委託給另一個物件來處理。委託模式是一項基本技巧,許多其他的模式,如狀態模式、策略模式、訪問者模式本質上是在更特殊的場合採用了委託模式。委託模式使得我們可以用聚合來替代繼承。

Java中委託:

interfacePrinter{
voidprint(Strings)
}
classRealPrinterimplementsPrinter{
@override
publicvoidprint(Strings){
System.out.println(s);
}
}

classPrintImplimplementsPrinter{
//委託物件
RealPrinterrp=newRealPrinter();
@override
publicvoidprint(Strings){
rp.print(s);
}
}

classDemo{
publicstaticvoidmain(String[]args){
Printerp=newPrintImpl();
p.print("helloworld");
}
}

Kotlin:

interfacePrinter{
funprint(s:String)
}

classRealPrinter:Printer{
overridefunprint(s:String){
println(s)
}
}

classPrintImpl(p:Printer):Printerbyp

funmain(args:Array<String>){
valps=PrintImpl(RealPrinter())
ps.print("helloworld")
}

by表示 p 將會在 PrintImpl 中內部儲存, 並且編譯器將自動生成轉發給 p 的所有 Printer 的方法。

委託屬性

有一些常見的屬性型別,雖然我們可以在每次需要的時候手動實現它們, 但是如果能夠為大家把他們只實現一次並放入一個庫會更好。例如包括:

  • 延遲屬性(lazy properties): 其值只在首次訪問時計算;

  • 可觀察屬性(observable properties): 監聽器會收到有關此屬性變更的通知;

  • 把多個屬性儲存在一個對映(map)中,而不是每個存在單獨的欄位中。

為了涵蓋這些(以及其他)情況,Kotlin 支援 委託屬性 。

委託屬性的語法是:

val/var:<型別>by<表示式>

在 by 後面的表示式是該 委託, 因為屬性對應的 get()(和 set())會被委託給它的 getValue() 和 setValue() 方法。

標準委託:

Kotlin 標準庫為幾種有用的委託提供了工廠方法。

  1. 延遲屬性 Lazy
    lazy() 接受一個 lambda 並返回一個 Lazy

    例項的函式,返回的例項可以作為實現延遲屬性的委託:第一次呼叫 get() 會執行已傳遞給 lazy() 的 lambda 表示式並記錄結果, 後續呼叫 get() 只是返回記錄的結果。例如:
vallazyValue:Stringbylazy{
println("computed!")
"hello"
}

funmain(args:Array<String>){
println(lazyValue)
println(lazyValue)
}
//輸出:
//computed!
//hello
//hello
  1. 可觀察屬性 Observable
    Delegates.observable() 接受兩個引數:初始值和修改時處理程式(handler)。每當我們給屬性賦值時會呼叫該處理程式(在賦值後執行)。它有三個引數:被賦值的屬性、舊值和新值:

classUser{
varname:StringbyDelegates.observable(""){
prop,old,new->
println("$old->$new")
}
}
funmain(args:Array<String>){
valuser=User()
user.name="first"
user.name="second"
}
//輸出
//->first
//first->second

如果想攔截賦的新值,並根據你是不是想要這個值來決定是否給屬性賦新值,可以使用 vetoable() 取代 observable(),接收的引數和 observable 一樣,不過處理程式 返回值是 Boolean 來決定是否採用新值,即在屬性被賦新值生效之前 會呼叫傳遞給 vetoable 的處理程式。例如:

classUser{
varnum:IntbyDelegates.vetoable(0){property,oldValue,newValue->
newValue>oldValue
}
}
funmain(args:Array<String>){
valuser=User()
user.num=10
println(user.num)//10>0,所以接受新值,輸出10

user.num=5
println(user.num)//5<10,處理程式返回false,拒絕新值,輸出10
}
  1. 把屬性存在map 中
    一個常見的用例是在一個對映(map)裡儲存屬性的值。這經常出現在像解析 JSON 或者做其他“動態”事情的應用中。在這種情況下,你可以使用對映例項自身作為委託來實現委託屬性。

例如:

classUser(map:Map){valname:Stringbymapvalage:Intbymap
}funmain(args:Array<String>){valuser=User(mapOf("name"to"whx","age"to18))
println(user.name)//whx
println(user.age)//18
}

在上例中,委託屬性會從建構函式傳入的map中取值(通過字串鍵——屬性的名稱),如果遇到宣告的屬性名在map 中找不到對應的key 名,或者key 對應的value 值的型別與宣告的屬性的型別不一致,會丟擲異常。

行內函數

當一個函式被宣告為inline時,它的函式體是內聯的,也就是說,函式體會被直接替換到函式被呼叫地方

inline函式(行內函數)從概念上講是編譯器使用函式實現的真實程式碼來替換每一次的函式呼叫,帶來的最直接的好處就是節省了函式呼叫的開銷,而缺點就是增加了所生成位元組碼的尺寸。基於此,在程式碼量不是很大的情況下,我們是否有必要將所有的函式定義為內聯?讓我們分兩種情況進行說明:

  1. 將普通函式定義為內聯:眾所周知,JVM內部已經實現了內聯優化,它會在任何可以通過內聯來提升效能的地方將函式呼叫內聯化,並且相對於手動將普通函式定義為內聯,通過JVM內聯優化所生成的位元組碼,每個函式的實現只會出現一次,這樣在保證減少執行時開銷的同時,也沒有增加位元組碼的尺寸;所以我們可以得出結論,對於普通函式,我們沒有必要將其宣告為行內函數,而是交給JVM自行優化。

  2. 將帶有lambda引數的函式定義為內聯:是的,這種情況下確實可以提高效能;但在使用的過程中,我們會發現它是有諸多限制的,讓我們從下面的例子開始展開說明:

inlinefundoSomething(action:()->Unit){
println("BeforedoSomething...")
action()
println("AfterdoSomething...")
}

假如我們這樣呼叫doSomething:

funmain(args:Array<String>){
doSomething{
pringln("HelloWorld")
}
}

上面的呼叫會被編譯成:

funmain(args:Array<String>){
println("BeforedoSomething...")
println("HelloWorld")
println("AfterdoSomething...")
}

從上面編譯的結果可以看出,無論doSomething函式還是action引數都被內聯了,很棒,那讓我們換一種呼叫方式:

funmain(args:Array<String>){
valaction:()->Unit={println("HelloWorld")}
doSomething(action)
}

上面的呼叫會被編譯成:

funmain(args:Array<String>){
println("BeforedoSomething...")
action()
println("AfterdoSomething...")
}

doSomething函式被內聯,而action引數沒有被內聯,這是因為以函式型變數的形式傳遞給doSomething的lambda在函式的呼叫點是不可用的,只有等到doSomething被內聯後,該lambda才可以正常使用。

通過上面的例子,我們對lambda表示式何時被內聯做一下簡單的總結:

  1. 當lambda表示式以引數的形式直接傳遞給行內函數,那麼lambda表示式的程式碼會被直接替換到最終生成的程式碼中。

  2. 當lambda表示式在某個地方被儲存起來,然後以變數形式傳遞給行內函數,那麼此時的lambda表示式的程式碼將不會被內聯。

上面對lambda的內聯時機進行了討論,消化片刻後讓我們再看最後一個例子:

inlinefundoSomething(action:()->Unit,secretAction:()->Unit){
action()
doSomethingSecret(secretAction)
}

fundoSomethingSecret(secretAction:()->Unit){
}

上面的例子是否有問題?是的,編譯器會丟擲“Illegal usage of inline-parameter”的錯誤,這是因為Kotlin規定行內函數中的lambda引數只能被直接呼叫或者傳遞給另外一個行內函數,除此之外不能作為他用;那我們如果確實想要將某一個lambda傳遞給一個非行內函數怎麼辦?我們只需將上述程式碼這樣改造即可:

inlinefundoSomething(action:()->Unit,noinlinesecretAction:()->Unit){
action()
doSomethingSecret(secretAction)
}

fundoSomethingSecret(secretAction:()->Unit){
}

很簡單,在不需要內聯的lambda引數前加上noinline修飾符就可以了。

以上便是我對行內函數的全部理解,通過掌握該特性的執行機制,相信大家可以做到在正確的時機使用該特性,而非濫用或因恐懼棄而不用。

Kotlin下單例模式

餓漢式實現

//Java實現
publicclassSingletonDemo{
privatestaticSingletonDemoinstance=newSingletonDemo();
privateSingletonDemo(){

}
publicstaticSingletonDemogetInstance(){
returninstance;
}
}

//Kotlin實現
objectSingletonDemo

懶漢式

//Java實現
publicclassSingletonDemo{
privatestaticSingletonDemoinstance;
privateSingletonDemo(){}
publicstaticSingletonDemogetInstance(){
if(instance==null){
instance=newSingletonDemo();
}
returninstance;
}
}
//Kotlin實現
classSingletonDemoprivateconstructor(){
companionobject{
privatevarinstance:SingletonDemo?=nullget(){
if(field==null){
field=SingletonDemo()
}
returnfield
}
funget():SingletonDemo{
//細心的小夥伴肯定發現了,這裡不用getInstance作為為方法名,是因為在伴生物件宣告時,內部已有getInstance方法,所以只能取其他名字
returninstance!!
}
}
}

上述程式碼中,我們可以發現在Kotlin實現中,我們讓其主建構函式私有化並自定義了其屬性訪問器,其餘內容大同小異。

  • 如果有小夥伴不清楚Kotlin建構函式的使用方式。請點選 - - - 建構函式

  • 不清楚Kotlin的屬性與訪問器,請點選 - - -屬性和欄位

執行緒安全的懶漢式

//Java實現
publicclassSingletonDemo{
privatestaticSingletonDemoinstance;
privateSingletonDemo(){}
publicstaticsynchronizedSingletonDemogetInstance(){//使用同步鎖
if(instance==null){
instance=newSingletonDemo();
}
returninstance;
}
}
//Kotlin實現
classSingletonDemoprivateconstructor(){
companionobject{
privatevarinstance:SingletonDemo?=null
get(){
if(field==null){
field=SingletonDemo()
}
returnfield
}
@Synchronized
funget():SingletonDemo{
returninstance!!
}
}

}

大家都知道在使用懶漢式會出現執行緒安全的問題,需要使用使用同步鎖,在Kotlin中,如果你需要將方法宣告為同步,需要新增@Synchronized註解。

雙重校驗鎖式

//Java實現
publicclassSingletonDemo{
privatevolatilestaticSingletonDemoinstance;
privateSingletonDemo(){}
publicstaticSingletonDemogetInstance(){
if(instance==null){
synchronized(SingletonDemo.class){
if(instance==null){
instance=newSingletonDemo();
}
}
}
returninstance;
}
}
//kotlin實現
classSingletonDemoprivateconstructor(){
companionobject{
valinstance:SingletonDemobylazy(mode=LazyThreadSafetyMode.SYNCHRONIZED){
SingletonDemo()}
}
}

哇!小夥伴們驚喜不,感不感動啊。我們居然幾行程式碼就實現了多行的Java程式碼。其中我們運用到了Kotlin的延遲屬性 Lazy。

Lazy內部實現

publicfunlazy(mode:LazyThreadSafetyMode,initializer:()->T):Lazy=when(mode){
LazyThreadSafetyMode.SYNCHRONIZED->SynchronizedLazyImpl(initializer)
LazyThreadSafetyMode.PUBLICATION->SafePublicationLazyImpl(initializer)
LazyThreadSafetyMode.NONE->UnsafeLazyImpl(initializer)
}

觀察上述程式碼,因為我們傳入的mode = LazyThreadSafetyMode.SYNCHRONIZED,
那麼會直接走 SynchronizedLazyImpl,我們繼續觀察SynchronizedLazyImpl。

Lazy介面

SynchronizedLazyImpl實現了Lazy介面,Lazy具體介面如下:

publicinterfaceLazy<outT>{
//當前例項化物件,一旦例項化後,該物件不會再改變
publicvalvalue:T
//返回true表示,已經延遲例項化過了,false表示,沒有被例項化,
//一旦方法返回true,該方法會一直返回true,且不會再繼續例項化
publicfunisInitialized():Boolean
}

繼續檢視SynchronizedLazyImpl,具體實現如下:

SynchronizedLazyImpl內部實現

privateclassSynchronizedLazyImpl<outT>(initializer:()->T,lock:Any?=null):Lazy,Serializable{privatevarinitializer:(()->T)?=[email protected]privatevar_value:Any?=UNINITIALIZED_VALUE//finalfieldisrequiredtoenablesafepublicationofconstructedinstanceprivatevallock=lock?:thisoverridevalvalue:Tget(){val_v1=_value//判斷是否已經初始化過,如果初始化過直接返回,不在呼叫高階函式內部邏輯if(_v1!==UNINITIALIZED_VALUE){@Suppress("UNCHECKED_CAST")return_v1asT
}returnsynchronized(lock){val_v2=_valueif(_v2!==UNINITIALIZED_VALUE){@Suppress("UNCHECKED_CAST")(_v2asT)
}else{valtypedValue=initializer!!()//呼叫高階函式獲取其返回值
_value=typedValue//將返回值賦值給_value,用於下次判斷時,直接返回高階函式的返回值
initializer=null
typedValue
}
}
}//省略部分程式碼
}

通過上述程式碼,我們發現 SynchronizedLazyImpl 覆蓋了Lazy介面的value屬性,並且重新了其屬性訪問器。其具體邏輯與Java的雙重檢驗是類似的。

到裡這裡其實大家還是肯定有疑問,我這裡只是例項化了SynchronizedLazyImpl物件,並沒有進行值的獲取,它是怎麼拿到高階函式的返回值呢?。這裡又涉及到了委託屬性

委託屬性語法是:val/var : by 。在 by 後面的表示式是該 委託, 因為屬性對應的 get()(和 set())會被委託給它的 getValue() 和 setValue() 方法。屬性的委託不必實現任何的介面,但是需要提供一個 getValue() 函式(和 setValue()——對於 var 屬性)。

而Lazy.kt檔案中,聲明瞭Lazy介面的getValue擴充套件函式。故在最終賦值的時候會呼叫該方法。

@kotlin.internal.InlineOnly
//返回初始化的值。
publicinlineoperatorfunLazy.getValue(thisRef:Any?,property:KProperty):T=value

靜態內部類式

//Java實現
publicclassSingletonDemo{
privatestaticclassSingletonHolder{
privatestaticSingletonDemoinstance=newSingletonDemo();
}
privateSingletonDemo(){
System.out.println("Singletonhasloaded");
}
publicstaticSingletonDemogetInstance(){
returnSingletonHolder.instance;
}
}
//kotlin實現
classSingletonDemoprivateconstructor(){
companionobject{
valinstance=SingletonHolder.holder
}

privateobjectSingletonHolder{
valholder=SingletonDemo()
}

}

靜態內部類的實現方式,也沒有什麼好說的。Kotlin與Java實現基本雷同。

補充

在該篇文章結束後,有很多小夥伴諮詢,如何在Kotlin版的Double Check,給單例新增一個屬性,這裡我給大家提供了一個實現的方式。(不好意思,最近才抽出時間來解決這個問題)

classSingletonDemoprivateconstructor(privatevalproperty:Int){
//這裡可以根據實際需求發生改變
companionobject{
@Volatileprivatevarinstance:SingletonDemo?=null
fungetInstance(property:Int)=
instance?:synchronized(this){
instance?:SingletonDemo(property).also{instance=it}
}
}
}

其中關於?:操作符,如果 ?: 左側表示式非空,就返回其左側表示式,否則返回右側表示式。請注意,當且僅當左側為空時,才會對右側表示式求值。

Kotlin 智慧型別轉換

對於子父類之間的型別轉換

  • 先看這樣一段 Java 程式碼

publicclassPerson{
}

publicclassStudentextendsPerson{
publicvoidstudy(){
System.out.println("我在學習一門新的語言 Kotlin !");
}
}

publicstaticvoidmain(String[]args){
Personperson=newStudent();
if(personinstanceofStudent){
((Student)person).study();
}
}
  • 儘管在 main 函式中,對 person 這個物件進行了型別判斷,但是在使用的時候還是需要強制轉換成 Student 型別,這樣是不是很不智慧?

  • 同樣的情況在 Kotlin 中就變得簡單多了

funmain(args:Array<String>){
valperson:Person=Student()
if(personisStudent){
person.study()
}
}
  • 在 Kotlin 中,只要對型別進行了判斷,就可以直接通過父類的物件去呼叫子類的函數了

安全的型別轉換

  • 還是上面的那個例子,如果我們沒有進行型別判斷,並且直接進行強轉,會怎麼樣呢?

publicstaticvoidmain(String[]args){
Personperson=newPerson();
((Student)person).study();
}
  • 結果就只能是 Exception in thread "main" java.lang.ClassCastException

  • 那麼在 Kotlin 中是不是會有更好的解決方法呢?

valperson:Person=Person()
valstudent:Student?=personas?Student
println(student)//null
  • 在轉換操作符後面新增一個 ?,就不會把程式 crash 掉了,當轉化失敗的時候,就會返回一個 null

在空型別中的智慧轉換

  • 需要提前瞭解 Kotlin 型別安全的相關知識(Kotlin 中的型別安全(對空指標的優化處理))

valaString:String?="HelloKotlin"
if(aStringisString){
println(aString.length)
}
  • aString 在定義的時候定義成了有可能為 null,按照之前的寫法,我們需要這樣寫

valaString:String?="HelloKotlin"
println(aString?.length)
  • 但是已經進行了是否為 String 型別的判斷,所以就一定 不是 空型別了,也就可以直接輸出它的長度了

T.()->Unit 、 ()->Unit

在做kotlin開發中,經常看到一些系統函式裡,用函式作為引數

publicinlinefunT.apply(block:T.()->Unit):T
{
block()
returnthis
}

publicinlinefunapply(block:()->Unit)
{
block()
}

.()-Unit與()->Unit的區別是我們呼叫時,在程式碼塊裡面寫this,的時候,兩個this代表的含義不一樣,T.()->Unit裡的this代表的是自身例項,而()->Unit裡,this代表的是外部類的例項

推薦閱讀
對 Kotlin 與 Java 程式語言的思考
使用 Kotlin 做開發一個月後的感想

e0ed7c257ed12c1a9718ba8dec951d32.png

掃一掃 關注我的公眾號 如果你想要跟大家分享你的文章,歡迎投稿~