天天看點

Kotlin基礎知識

class empty

java 中即使是空類,也需要寫類體

關鍵字constructor,當主構造函數沒有任何注解或者可見性修飾符,可以省略。

class person

constructor(name: string, age: int)

等價于

       class person(name: string, age:

int)

主構造函數:定義在類頭中的構造函數。

次構造函數:定義在類體中的構造函數。

在 kotlin 中,類中可以聲明主構造函數(零個或一個)和次構造函數(零個或多個)。次構造函數可以使用this關鍵字調用其他的構造函數,沒有主構造函數也可以寫次構造函數,這時次構造函數不需要委托給主構造函數(無主構造函數)。

l  主構造函數隻能調用父類的構造函數

l  次構造函數可以調用其他次構造函數(次構造函數不能循環調用),但是如果存在主構造函數,最終需要調用主構造函數。

l  如果一個類中,沒有主構造函數,但是有init初始化塊和次構造函數,那麼在調用這個類的次構造函數時,init初始化塊會在次構造函數執行前執行。

在主構造函數中定義的屬性,如果有var或val修飾,就相當于類的屬性,可以在類内部使用,如果沒有修飾,則隻能在init初始化塊和類體内聲明的屬性初始化器中使用。次構造函數中的參數不能用val與var修飾。

類頭中聲明的主構造函數中不能包含任何的代碼,隻能放在init初始化塊中。

q:與swift的差別?

l  kotlin中隻能有一個主構造器,多個次構造器。

l  swift中可以有多個指定構造器,多個便利構造器。

q:kotlin中為什麼有主構造函數和次構造函數之分?

個人了解:在主構造函數中,可以聲明并初始化類中全部的存儲屬性,可以簡化類結構。而且次構造函數最終需要調用主構造函數,這樣可以保證類中的存儲屬性都可以完成初始化。但如果次構造函數也可以聲明成員屬性,那麼就不能保證所有的屬性都可以初始化,類結構會比較混亂。是以構造函數需要有主次之分。

沒有主構造函數,隻有多個次構造函數的情況,是為了友善從java轉到kotlin開發的程式員使用。

下列條件滿足一條即可:

l  類中沒有任何一個構造函數(主構造函數、次構造函數)

l  主構造函數所有參數都有預設值。

l  有一個次構造函數所有參數都有預設值。

       注意:

a)       

b)       

次構造函數的參數都有預設值的時候,不會額外生成無參構造函數,隻是在調用的時候,使用參數的預設值。

預設可見為public,可以用private修飾  ---->引申,java中單例

a:在代碼中可以使用無參構造函數建立對象。應該避免這麼做!!!

在kotlin中屬性分為隻讀屬性和可變屬性兩種,隻讀屬性用關鍵字val聲明,可變屬性用關鍵字var 聲明。

kotlin中可以給屬性設定自定義的get和set通路器。多用于計算屬性。

get與set可以任意設定,隻有get、隻有set或者都有。

l  當getter可以推斷出屬性類型時,可以省略類型聲明。

l  在get與set内部,不可以用屬性名本身執行語句(互相引用,導緻棧溢出),如果想使用本身屬性值時,需要用幕後字段field。

l  可以對get與set進行可見性修飾和加注解

getter必須與屬性可見性一緻

setter可以随意設定,但是不會超出類的可見性

get() = 可以使用函數執行結果指派

set(value) = 後面可以加if、when、try/catch表達式

kotlin 中類不能有字段。然而,當使用自定義通路器時,有時有一個幕後字段(backing field)有時是必要的。為此 kotlin 提供一個自動幕後字段,它可通過使用 field 辨別符通路。

field 辨別符隻能用在屬性的通路器内。

如果屬性至少一個通路器使用預設實作,或者自定義通路器通過 field 引用幕後字段,将會為該屬性生成一個幕後字段。

個人了解:類似于幕後字段的手動實作,可控性強。

已知值的屬性可以使用 const 修飾符标記為編譯期常量,可以使用在注解中。這些屬性需要滿足以下要求:

l  指定定義在頂層、 object或伴生對象中;

l  用 string 或原生類型 值初始化;

l  沒有自定義 getter。

關鍵字lateinit,該修飾符隻能用于在類體中(不是在主構造函數中)聲明的 var 屬性,并且僅當該屬性沒有自定義 getter 或 setter 時。該屬性必須是非空類型,并且不能是原生類型。

在使用延遲初始化屬性之前,必須要初始化,否則将會抛出異常。

使用延遲初始化屬性,是因為有些情況下,在聲明屬性的時候不能确定該屬性的初始化值,但是在後續的程式中,一定可以為其設定一個初始化值。lateinit修飾的屬性,需要程式員保證非空!

使用場景:在android中,需要聲明頁面的元件,在沒有findviewbyid時,需要設定一個null初始化值,如果用lateinit修飾,則可以不設定null。

在後面會有一個延遲屬性,隻可以用val聲明,一般用于聲明一些初始化耗時的計算屬性,隻有在第一次通路時計算并将計算結果儲存為屬性值,再次通路時,會直接使用儲存的值,不會再次計算。示例代碼如下:

引申:聲明屬性時,預設初始化值在什麼情況下可以不設定?

l  屬性用abstract或lateinit修飾時。

l  聲明在接口中的屬性

l  擴充的屬性

l  屬性的所有自定義通路器都沒有用過幕後字段field

程式執行結果如下:

      用無傳回值的函數給屬性指派:          kotlin.unit

      用有傳回值的函數給屬性指派:          有傳回值的函數

      用lambda表達式給屬性指派:           () -> kotlin.string

      用lambda表達式執行結果給屬性指派:   運作的lambda表達式

      用匿名函數給屬性指派:                ()

-> kotlin.string

      用匿名函數執行結果給屬性指派:        執行的匿名函數

繼承是強耦合的!

kotlin中所有類都隐式繼承自any。類的預設修飾符是final,如果想要可以被繼承,那麼需要顯式聲明為open。

如果子類中不存在主構造函數時,可以在子類的次構造函數中使用super關鍵字初始化其基類型,調用父類的構造函數。

子類的構造函數最終要調用父類構造函數。

l  當子類中存在主構造函數或者不存在任何構造函數時,需要在類頭初始化父類,父類後有括号。

l  當子類中隻存在次構造函數時,需要在次構造函數後面用super調用父類的構造函數,這時,父類後無需括号。當父類可以使用無參方式調用構造函數初始化時,super關鍵字可以省略,這時,預設調用父類該構造函數。

差別:

java中,構造函數沒有主次之分,是以調用任何一個父類構造函數都可以。

swift中,一個指定構造器必須調用直接父類的指定構造器,一個便利構造器隻能調用目前類的其他構造器,一個便利構造器必須最終調用一個指定構造器。

kotlin中,存在主構造函數時,與swift相同,當不存在主構造函數時,次構造函數可以調用父類中任何一個構造函數。

在初始化子類時,會按照目前類中構造方法的調用順序,反向依次執行。

l  當子類不存在主構造函數時,次構造函數可以直接調用父類的構造函數。

l  當子類存在主構造函數時,隻能由主構造函數調用父類的構造函數。

父類中方法預設修飾符是final,如果想要可以被子類重寫,需要顯式open,子類中重寫時,需要加override關鍵字,java中不加關鍵字也不會報錯。

如果想要禁止再次被重寫,那麼需要顯式加上final。

構造函數不能被重寫。

與覆寫函數類似,父類中聲明為open的屬性,可以在子類中使用override關鍵字重寫。

父類中的val屬性可以在子類中重寫為var,但是var屬性不可以被重寫為val的。

當父類中與接口中同名的方法為final修飾時,在子類中無法重寫同名方法,會報錯。

當父類與接口有同名函數,并且接口中的函數沒有預設實作時:

不重寫同名函數,會使用從父類中繼承來的函數,作為接口函數的實作。

如果重寫,則可以使用super. functionname ()來調用父類函數。

kotlin中:如果接口同名函數有預設實作,那麼必須在子類重寫該方法,因為該子類繼承了較多的實作。在生産過程中需要盡量避免該現象的産生。

l  抽象類與類中的函數不需要open修飾,就可以被繼承。

l  可以用一個抽象成員覆寫一個非抽象成員。

l  可省略伴生對象的名稱,系統預設使用companion作為伴生對象名稱

l  可以繼承類、實作接口

l  可以通過在伴生對象中建立外部類的執行個體來調用外部類的函數和屬性。

q:取消static而引入伴生對象的意義:

a:把所有的靜态的屬性和方法都集中到了一起,代碼易讀性高。

引申:單例模式的實作

伴生對象   val c = aa.instance

object      val c = c

kotlin中接口的函數可以有預設的實作。

l  有預設實作的函數可以不在實作類中重寫

l  有預設實作的函數,可以再實作類中的init塊中、次構造函數中、成員函數中、給屬性指派時調用。

它可以有屬性但必須聲明為抽象或提供通路器實作。

l  抽象的屬性:需要在實作類中重寫

l  提供getter(可以加setter),可以用作計算屬性

與繼承的覆寫規則相同,使用<超類型名>.functionname()。

類、對象、接口、構造函數、方法、屬性和它們的 setter 都可以有 可見性修飾符。

(getter 總是與屬性有着相同的可見性。)

在 kotlin 中有這四個可見性修飾符:private、 protected、 internal 和 public。

如果沒有顯式指定修飾符的話,預設可見性是 public。

l  如果你不指定任何可見性修飾符,預設為 public,這意味着你的聲明将随處可見;

l  private用于頂層聲明時,于目前檔案内可見;用于類中聲明時,于目前類中可見;

l  internal用于頂層聲明時,它會在相同子產品内随處可見;用于類中聲明時,即使類的修飾符為public,用internal修飾的對象(成員屬性、成員函數等),在其它子產品也是不可見的;

l  protected 本類與子類中可見,不适用于頂層聲明。

可見性修飾符作用域如下:

作用域

其他module

目前module

目前檔案

本類

子類

public

internal

private

protected

kotlin中能夠擴充一個類的新功能而無需繼承該類或使用像裝飾者這樣的任何類型的設計模式,(僅)支援擴充函數和屬性。

靜态解析:在編譯時,就已經确定類型。

動态解析:在編譯時不确定類型,在執行時才會确認到底是什麼類型(比如:多态中的繼承,父類中定義抽象方法,每個子類有不同的實作)。

按照調用的擴充函數所定義的接收參數類型決定,該調用哪一個類的擴充(父子類)。

成員函數:被擴充的類原有的函數

l  當擴充函數與成員函數同名同參時,調用函數,執行成員函數。

l  擴充函數可以重載成員函數。

l  可以給子類和父類擴充相同名稱的函數。

l  不可以擴充構造函數,擴充構造函數會改變原有的類。

l  擴充函數需要有函數體。

由于擴充沒有實際的将成員插入類中,是以對擴充屬性來說幕後字段是無效的。這就是為什麼擴充屬性不能有初始化器。他們的行為隻能由顯式提供的 getters/setters 定義(隻能擴充計算屬性)。

不能擴充類中已有的屬性。

對伴生對象、内部類、嵌套類、接口以及java中定義的類和接口進行擴充。

l  對伴生對象、内部類、嵌套類的擴充方式:外部類.擴充類.方法或屬性,伴生對象如果省略了類名,則用companion代替。

l  對接口擴充,與普通擴充相同,擴充函數需要有函數體。

l  對java中定義的類與接口的擴充與在kotlin中相同。

l  要使用所定義包之外的一個擴充,我們需要在調用方導入。

注意:

在頂層定義的擴充,可以在任意可以見到目前擴充的地方使用(可見性修飾符修飾)。

在一個類中給其他類進行擴充,那麼這些擴充,隻能在目前類或者目前類的子類中使用,不可以在類外部使用。

q1:給一個類擴充兩個相同名稱的函數會怎樣?

如果在頂層擴充,将會報錯,不可以有這樣的寫法。

如果擴充聲明為成員,在父類與子類中分别擴充,可以override。

q2:擴充一個與父類成員函數相同的函數會怎樣?

無效果,因為子類繼承了父類的成員函數,調用時會執行子類中的成員函數。

在java中的model類是把有關系的一組資料封裝在一起的類檔案,通常model類中會隻定義屬性和get、set方法,而且每個model類都是一個單獨的檔案。kotlin中對此進行了優化,将這些隻儲存資料的類定義為資料類并用data關鍵字修飾,一個檔案中可以聲明多個資料類。

編譯器自動從主構造函數中聲明的所有屬性導出以下成員:

l  equals()/hashcode() 對,

l  tostring() 格式是 "user(name=john, age=42)",

l  componentn()函數 按聲明順序對應于所有屬性(多用于解構聲明),

l  copy() 函數,複制出克隆對象,可以在複制的時候,對儲存的屬性進行修改

資料類可繼承類與實作接口。

可以在資料類中定義自己的方法,可擴充。

相當于枚舉類型的擴充,但可以比枚舉類型做更多的工作。

密封類的子類隻能與密封類定義在同一個檔案内,但繼承密封類子類的類(間接繼承者)可以放在任何位置,不僅限于同檔案内。

密封類不能執行個體化,但密封類的子類可以。

繼續閱讀