首發(fā)于公眾號: DSGtalk1989
5.Kotlin 類和對象
-
構(gòu)造器
kotlin中一個類只能有一個主構(gòu)造器和一個或多個次構(gòu)造器西轩。主構(gòu)造器可以直接跟在
class
定義的類名后面但是沒有方法體,如下:class Person constructor(s : String) { } //也可以寫成這樣考余,記得,沒有空格 class Person(s : String){ } //一旦構(gòu)造函數(shù)存在修飾符或者是注解的情況下靠益,我們就不能省去constructor的方法名 class Child public constructor(s : String){ }
以上的都是主構(gòu)造函數(shù)液肌,次構(gòu)造函數(shù)定義在類中,并且kotlin強制規(guī)定邻耕,次構(gòu)造函數(shù)必須要調(diào)用主構(gòu)造函數(shù),如下:
class Person constructor(s : String) { constructor(i : Int) : this("123"){ } } //也可以沒有主構(gòu)造器燕鸽,如下的方式就是直接起的次構(gòu)造器 //只有這種情況下兄世,次構(gòu)造器不需要再調(diào)用主構(gòu)造器,所以一般如下這種方式跟我們的java習(xí)慣比較像 class Person{ constructor(){ } }
-
private啊研,public御滩,protected,internal
前面三個大家比較熟悉了党远,在java中都有削解,但是
internal
是kotlin中才引入的,叫做模塊內(nèi)可見沟娱,即同一個module
中可見氛驮。我們分別來看下,用這四個修飾符來描述屬性所帶來的編譯區(qū)別济似。
//kt private var a = "a" public var b = "b" protected var c = "c" internal var d = "d" //decompiled private String a; @NotNull private String b; @NotNull private String c; @NotNull private String d; @NotNull public final String getB() { return this.b; } public final void setB(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.b = var1; } @NotNull protected final String getC() { return this.c; } protected final void setC(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.c = var1; } @NotNull public final String getD$app_debug() { return this.d; } public final void setD$app_debug(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.d = var1; }
總結(jié)一下矫废,就是不管是哪一個修飾符,最終經(jīng)過編譯之后生成的在java中的參數(shù)描述都是
private
的砰蠢,修飾符真正造成的區(qū)別是在編譯了自后的get
和set
的方法不同蓖扑。private
的話,就不會生成get
和set
方法台舱,因為對于這個參數(shù)來說律杠,是外部不可訪問的。public
和protected
就是相應(yīng)的set
和get
竞惋。而internal
則是public
的setD$app_debug
和getD$app_debug
方法俩功。我們可以認(rèn)為這兩個方法,在model
中都是可以被訪問的碰声。 -
init關(guān)鍵字
上面說到主構(gòu)造器直接寫在類名之后是沒有方法體的,因此一旦我們想要在構(gòu)造函數(shù)中做一些初始化的操作熬甫,就需要挪到init中實現(xiàn)了胰挑。
class Person constructor(firstName: String) { init { println("FirstName is $firstName") } }
-
getter和setter
跟java差的有點多,首先屬性定義前面說過了,kotlin中g(shù)etter和setter直接定義在屬性下方瞻颂,由于kotlin的本身屬性的直接訪問性豺谈,只要你創(chuàng)建的是public的屬性,都可以直接獲取到屬性值即get方法和修改屬性值即set方法贡这。
所以免去了為了
fastjson
而專門去寫的setter
和getter
茬末,但是一旦你需要在getter
和setter
時做一些其他的操作,我們就需要去顯示的寫出get
和set
了var sex = "boy" get() { return "girl" } set(value) { when { value.contains("girl") -> field = "boy" } } var age = 16 get() = field + 10 private set(value) = action(value) fun action(int: Int) { }
get
和set
可以直接包含方法體盖矫,也可以直接通過等號的方式鏈到單行表達(dá)式或者方法丽惭。
即我們認(rèn)為,一旦觸發(fā)取值和賦值的時候會做相應(yīng)的操作辈双。其中field
就是指的他自己责掏。>>注:field的重要性<<
在kotlin中,我們定義了一個參數(shù)
name
然后湃望,只要去調(diào)用他换衬,比如parent.name
或者說去對他進(jìn)行賦值name = "Tony"
最終都會被編譯成使用了get
和set
方法,如下//Observer.kt fun main(args : Array<String>){ var observer = Observer() observer.name = "Tony" var newName = observer.name } //Observer.decompiled.java public static final void main(@NotNull String[] args) { Intrinsics.checkParameterIsNotNull(args, "args"); Observer observer = new Observer(); //調(diào)用了set observer.setName("Tony"); //調(diào)用了get String newName = observer.getName(); }
所以我們在類中定義屬性的
setter
和getter
的時候证芭,如果直接操作屬性本身瞳浦,就會出現(xiàn)死循環(huán)。這就是field
的用途//kt var no: Int = 100 get() = no set(value) { if (value < 10) { // 如果傳入的值小于 10 返回該值 no = value } else { no = -1 // 如果傳入的值大于等于 10 返回 -1 } } //decompiled int no = 100; public int getNo() { return getNo();// Kotlin中的get() = no語句中出來了變量no废士,直接被編譯器理解成“調(diào)用getter方法” } public void setNo(int value) { if (value < 10) { setNo(value);// Kotlin中出現(xiàn)“no =”這樣的字樣叫潦,直接被編譯器理解成“這里要調(diào)用setter方法” } else { setNo(-1);// 在setter方法中調(diào)用setter方法,這是不正確的 } }
很顯然湃密,造成了死循環(huán)诅挑。
-
lateinit關(guān)鍵字
我們都知道kotlin中,在方法中定義屬性時泛源,我們必須進(jìn)行初始化的操作拔妥。而類中本身我們一開始并不知道他到底是什么,所以會有希望晚一點再初始化的需求达箍,這里就可以使用
lateinit
關(guān)鍵字來描述没龙,那我們就可以不用給出具體的初始化值,但是kotlin會要求你必須給出屬性的類型缎玫。lateinit var game : String
那么這個
game
我們可以之后再對其賦值硬纤。從kotlin 1.2開始已經(jīng)支持全局和局部變量都是用lateinit, 并且我們可以通過isInitialized來判斷是否已經(jīng)初始化過
-
抽象類
我們默認(rèn)定義的
class
都是final
的赃磨,無法被繼承的筝家。所以一旦需要這個class
能夠被繼承,我們需要加上open
關(guān)鍵字邻辉。如果這是個抽象類溪王,那我們需要添加abstract
關(guān)鍵字腮鞍。一旦被abstract
描述,就無需再加上open
了莹菱。open class Person(){ } abstract class Parent{ }
緊接著移国,另外幾種場景
-
方法是否可以被重寫
默認(rèn)方法都是
final
的,如果需要讓方法可以被重寫道伟,需要在方法前再加上open
所有我們平時在java中寫的一個單純的類實際上轉(zhuǎn)換成kotlin是如下這個樣子的:
open class Person constructor(s: String) { open fun getName(){} }
同樣的
abstract
也是這個意思迹缀,加載class
前面只是形容類,跟方法和屬性什么的一點關(guān)系都沒有 -
抽象屬性
這是一個比較新的東西蜜徽,因為java不支持抽象屬性祝懂。就是說,你要是繼承我娜汁,你就必須要初始化我所要求初始化的屬性嫂易。
abstract class Parent(ame : String){ abstract var ame : String } //兩種集成方式,一種是直接在構(gòu)造函數(shù)中對抽象屬性進(jìn)行復(fù)寫 //由于父類構(gòu)造需要傳一個字符串掐禁,所以在繼承時也需要直接傳入怜械,此處傳入的是Child1自己的構(gòu)造參數(shù)s class Child1 constructor(s: String, override var ame: String) : Parent(s) { } //一種是在類中對屬性進(jìn)行復(fù)寫 class Child2 constructor(s: String) : Parent(s) { override lateinit var ame: String } //如果子類沒有主構(gòu)造函數(shù),也可以通過次構(gòu)造函數(shù)調(diào)用`super`方法實現(xiàn) class Child: Parent { constructor() : super("s") override lateinit var ame: String }
-
-
嵌套類
直接在
class
內(nèi)部定義class
傅事,基本和java差不多class Outer { // 外部類 private val bar: Int = 1 class Nested { // 嵌套類 fun foo() = 2 } } fun main(args: Array<String>) { val demo = Outer.Nested().foo() // 調(diào)用格式:外部類.嵌套類.嵌套類方法/屬性 println(demo) // == 2 }
-
內(nèi)部類
在剛才嵌套類的基礎(chǔ)上加上
inner
的關(guān)鍵字申明缕允。class Outer { private val bar: Int = 1 var v = "成員屬性" /**嵌套內(nèi)部類**/ inner class Inner { fun foo() = bar // 訪問外部類成員 fun innerTest() { var o = this@Outer //獲取外部類的成員變量 println("內(nèi)部類可以引用外部類的成員,例如:" + o.v) } } }
唯一的區(qū)別在于內(nèi)部類持有了外部類的引用蹭越,可以通過
@外部類名
的方式障本,來訪問外部類的成員變量。 -
內(nèi)部類和嵌套類的區(qū)別
我們來看如下兩個嵌套類和內(nèi)部類的以及讓門各自編譯成class文件的例子
//Out.kt class Out{ class Inner{ } } //Out.decompiled.java public final class Out { public static final class Inner { } } //Out.kt class Out{ inner class Inner{ } } //Out.decompiled.java public final class Out { public final class Inner { } }
已經(jīng)很明顯了响鹃,
inner
之所以持有外部的引用驾霜,是因為他不是static
的。也就是說kotlin的class
默認(rèn)就是static final
的买置。調(diào)用嵌套類的方式與調(diào)用內(nèi)部類的方式差別也只是一個括號而已
fun main(args : Array<String>){ //內(nèi)部類調(diào)用 Out().Inner().method() //嵌套類的調(diào)用 Out1.Inner().method() }
其實比較容易理解粪糙,嵌套類是
static
的直接可以通過類名來進(jìn)行訪問嵌套類。 -
匿名內(nèi)部類
一般用在接口層面的很多忿项,我們通常知道的是傳參是個接口蓉冈,方法中調(diào)用了接口方法的形式,如下:
class Observer{ fun getIt(listener: Listener){ listener.onClick() } } interface Listener{ fun onClick() } fun main(args : Array<String>){ var observer = Observer() //注意轩触,此處的object是kotlin獨有的關(guān)鍵字 //不是隨便寫寫的寞酿,匿名內(nèi)部類必須通過這個關(guān)鍵字來申明 observer.getIt(object : Listener{ override fun onClick() { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } }) }
通常我們也可以直接使用
接口名
+lambda表達(dá)式
的方式來生成匿名內(nèi)部類,但條件是這個接口必須是函數(shù)式j(luò)ava接口脱柱,即只有一個抽象方法的java文件中定義的接口伐弹。比如我們基本碰到的所有的什么
OnclickListener
等等tv_case_id.setOnClickListener { View.OnClickListener{ } }
不過kotlin中定義的接口,我們就必須通過
object
的方式去實現(xiàn)了同時匿名內(nèi)部類我們可以單獨的拿出來進(jìn)行定義榨为,實際上我們可以把
object :
理解成一個匿名的內(nèi)部類實現(xiàn)了一個接口惨好,也就是說我們還可以實現(xiàn)多個接口椅邓,比如:open class A(x: Int) { public open val y: Int = x } interface B { …… } val ab: A = object : A(1), B { override val y = 15 }
通常我們在java中是無法做到匿名內(nèi)部類實現(xiàn)多個接口的,因為我們只能
new
一個接口出來昧狮。
更甚者說,我們很多時候甚至不需要這個object
去實現(xiàn)或者是繼承什么板壮,我們可以直接搞一個object
出來
fun foo() {
val adHoc = object {
var x: Int = 0
var y: Int = 0
}
print(adHoc.x + adHoc.y)
}
-
匿名對象最為函數(shù)的返回類型
我們上面是將匿名對象賦值給了對象逗鸣,我們還可以吧匿名對象直接賦值給方法,比如下面這個樣子绰精。
fun publicFoo() = object { val x: String = "x" }
這里涉及到公有還是私有的問題撒璧。
匿名對象我們一般只能用在私有域和本地。白話的說就是一旦變成了公有笨使,那就說誰都可以去調(diào)用卿樱,由于匿名對象只在生命的本地和私有域起作用,導(dǎo)致公有調(diào)用拿到的對象只能是匿名對象的超類(即父類硫椰,比如上面的
object : Listener
就是Listener
繁调,如果沒有顯式的定義超類就是Any
)那么這樣一來,就會導(dǎo)致匿名內(nèi)部類中定義的屬性是拿不到的靶草,比如上面的x
蹄胰,因為上面的object
并沒有顯式的定義超類,所以他返回的是Any
奕翔,而Any
是沒有x
屬性的. -
匿名對象訪問變量
在java中匿名內(nèi)部類想要訪問相應(yīng)的屬性變量必須要
final
才行裕寨,但是在kotlin中,我們直接可以訪問包含匿名對象作用域中的所有變量派继。fun countClicks(window: JComponent) { var clickCount = 0 var enterCount = 0 window.addMouseListener(object : MouseAdapter() { override fun mouseClicked(e: MouseEvent) { clickCount++ } override fun mouseEntered(e: MouseEvent) { enterCount++ } }) }
Kotlin學(xué)習(xí)筆記之 1 基礎(chǔ)語法
Kotlin學(xué)習(xí)筆記之 2 基本數(shù)據(jù)類型
Kotlin學(xué)習(xí)筆記之 4 循環(huán)控制
Kotlin學(xué)習(xí)筆記之 9 數(shù)據(jù)類與密封類
Kotlin學(xué)習(xí)筆記之 12 對象表達(dá)式和對象聲明
Kotlin學(xué)習(xí)筆記之 13 基礎(chǔ)操作符run宾袜、with、let驾窟、also庆猫、apply
Kotlin學(xué)習(xí)筆記之 14 包與導(dǎo)入
Kotlin學(xué)習(xí)筆記之 18 函數(shù)
Kotlin學(xué)習(xí)筆記之 19 高階函數(shù)與 lambda 表達(dá)式
Kotlin學(xué)習(xí)筆記之 20 內(nèi)聯(lián)函數(shù)
Kotlin學(xué)習(xí)筆記之 21 解構(gòu)聲明
Kotlin學(xué)習(xí)筆記之 28 協(xié)程基礎(chǔ)
Kotlin學(xué)習(xí)筆記之 29 上下文與調(diào)度器
Kotlin學(xué)習(xí)筆記之 30 協(xié)程取消與超時
Kotlin學(xué)習(xí)筆記之 31 協(xié)程掛起函數(shù)的組合