首發(fā)于公眾號: DSGtalk1989
6.Kotlin 繼承
-
Any
Any
是所有類的超類,就像java
中的Object
。但它并不等同于Object
,除了equals()``hashCode()``toString()
沒有其他任何成員。 -
繼承和實現(xiàn)':'
之前我們已經(jīng)知道了接口是
interface
,可被繼承的類是open
,抽象類是abstract
。跟java不同的是,java中
extends
是繼承,implements
是實現(xiàn)。而在kotlin中跟在:
后面,不管是接口還是類,沒有先后順序。也就是說:
既可以繼承也可以實現(xiàn),互相用,
隔開open class Person3 { open var sex: String = "unknow" init { println("基類初始化") } } interface Grand { fun make() } class Student : Grand, Person3() { override fun make() { } override var sex: String = "male" }
-
基類構(gòu)造和子類構(gòu)造
首先我們先看下最基礎(chǔ)的構(gòu)造,在經(jīng)過編譯之后分別是什么情況的
-
默認(rèn)定義
```js //kt class Person(name: String) { } //decompiled public final class Person { public Person(@NotNull String name) { //無視,這段代碼之后會出現(xiàn)很多次 Intrinsics.checkParameterIsNotNull(name, "name"); super(); } } ```
我們看到經(jīng)過編譯之后,就是定義了一個
final
的classPerson
,name
只是一個構(gòu)造函數(shù)的傳參而已 -
var
定義我們嘗試著在構(gòu)造傳參
name
前加一個var
看看,會發(fā)現(xiàn)編譯之后的差別不是一點點。```js //kt class Person(var name: String) { } //decompiled public final class Person { @NotNull private String name; @NotNull public final String getName() { return this.name; } public final void setName(@NotNull String var1) { Intrinsics.checkParameterIsNotNull(var1, "<set-?>"); this.name = var1; } public Person(@NotNull String name) { Intrinsics.checkParameterIsNotNull(name, "name"); super(); this.name = name; } } ```
編譯過程幫我們創(chuàng)建了一個同名的屬性name,并且提供了
name
的set
和get
方法,也就是說一旦主構(gòu)造函數(shù)中出現(xiàn)了var
(我這邊故意重點說了主構(gòu)造函數(shù),因為次構(gòu)造函數(shù)中無法使用var
),那么所描述的這個參數(shù)就可以變得在類中可以被訪問和修改,name
就變成了Person
的一部分了。
-
OK,我們接下去看一下兩種情況下作為基類被繼承了之后會有什么地方的不同。
前面我們有提到,kotlin中無論使用什么修飾符,對于參數(shù)本身來說,編譯成java代碼之后始終是private
的,只是通過get
和set
方法來體現(xiàn)修飾符,
如果我們的子類是這么定義的:
//kt
class Sun(name : String): Person(name){
}
//decompiled
public final class Sun extends Person {
public Sun(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super(name);
}
}
看起來完全沒有問題。這時候我們把name
寫上var
,我們先不反編譯,直接按照上面的理解,應(yīng)該是這樣的
```js
//kt
class Sun(var name : String): Person(name){
}
//decompiled
public final class Sun extends Person {
@NotNull
private String name;
@NotNull
public final String getName() {
return this.name;
}
public final void setName(@NotNull String var1) {
Intrinsics.checkParameterIsNotNull(var1, "<set-?>");
this.name = var1;
}
public Sun(@NotNull String name) {
Intrinsics.checkParameterIsNotNull(name, "name");
super();
this.name = name;
}
}
```
乍一看,沒有問題。但是實際上這么寫IDE直接會報錯。Sun
的方法getName
和setName
是public final
的,由于父類也有相同的方法getName
,setName
。犯了java的大忌,子類出現(xiàn)了父類的同名函數(shù),父類的函數(shù)還他么是final
的。
也就是說,除了父類是非final
字段,子類不能出現(xiàn)相同的字段。
綜上所述,只要不是open
形容的字段,都不能在子類出現(xiàn)重復(fù)的字段名。無論是val
還是var
。
-
var
和val
復(fù)寫關(guān)系val
無法復(fù)寫var
,不要當(dāng)做概念去背。前面我們說過,一個屬性必須要用
open
來形容才能被子類復(fù)寫。這是其一其二,kotlin中我們是直接去操作屬性的,因為編譯成java文件是通過
getter
和setter
方法做訪問和修改。var
是可以訪問并修改,val
只能訪問無法修改。父類中本身可以訪問并修改的,在子類中變得只能訪問了,是不允許的。繼承關(guān)系我們只能拓展而不能縮減,子類必須包含父類。
-
子類構(gòu)造函數(shù)的影響
首先,一個類
class
肯定會有一個構(gòu)造函數(shù)。那么對于子類來說需要注意什么呢。我們先來看一個隱式主構(gòu)造函數(shù)的類:
open class Parent{ }
我們前面說到,一旦沒有顯式的跟在類名后面申明
constructor
,或者()
,并且在類體中沒有出現(xiàn)次級構(gòu)造函數(shù)constructor
。我們就認(rèn)為有一個隱式的無參的主構(gòu)造函數(shù)。這時候我們繼承一下,并且嘗試著什么都不做
class Sun : Parent{ }
你會發(fā)現(xiàn)
Person
下面有根紅線報錯了,寫道This type has a constructor, and thus must be initialized here
父類有構(gòu)造器,必須要進(jìn)行初始化。也就是說我們必須顯式或者隱式的去調(diào)一次
Parent
的無參的構(gòu)造函數(shù)。方式有很多種,我們先看第一種:
class Sun : Parent(){ }
直接在父類后面來個括號即可。
你會發(fā)現(xiàn)加
()
和不加的前后經(jīng)過編譯得到的java文件是一樣的,都是public final class Sun extends Parent { }
實際上,這邊我們用有參構(gòu)造去講,會更加容易理解。比如我們針對
Parent
類添加一個有參的主構(gòu)造函數(shù),并且保留無參構(gòu)造函數(shù)。open class Parent(name : String){ }
子類分別調(diào)用和不調(diào)用父類的構(gòu)造函數(shù),所編譯出來的效果是明顯不同的。
//kt class Sun1 : Parent{ } class Sun2 : Parent(){ } class Sun3 : Parent("tom"){ } //decompiled public final class Sun1 extends Parent { } public final class Sun2 extends Parent { } public final class Sun3 extends Parent { public Sun3() { super("tom"); } }
Sun1
是無法編譯通過的,所以出來的decompiled
實際上是沒有意義,上面說過調(diào)用父類的無參構(gòu)造和不調(diào)用最終出來的結(jié)果是一樣的,所以我們直接看Sun2
即可
Sun2
和Sun3
連在一起看,首先我們知道,在java中,每一個類如果沒有單獨的去申明他的構(gòu)造函數(shù),那么他們都將有一個默認(rèn)的無參構(gòu)造函數(shù)。并且又有繼承,所以Sun2
實際上是這樣的
public final class Sun2 extends Parent {
public Sun2() {
super();
}
}
Kotlin學(xué)習(xí)筆記之 1 基礎(chǔ)語法
Kotlin學(xué)習(xí)筆記之 2 基本數(shù)據(jù)類型
Kotlin學(xué)習(xí)筆記之 4 循環(huán)控制
Kotlin學(xué)習(xí)筆記之 8 擴(kuò)展
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ù)的組合