在前面的章節中,詳細的詳解了類
的使用,可是因爲篇幅的限制,關於類的不少特性都沒有講解到。今天在這篇文章中,詳細的講解Kotlin
中類的特性。若是您對Kotlin
中的類尚未一個總體的瞭解的話,請參見我上一篇文章Kotlin——中級篇(一):類(class)詳解
衆所周知,Kotlin
是一門面向對象的開發語言。那麼他也有面向對象語言的特性。而面向對象的三大特性即封裝
、繼承
、多態
。這是每一門面向對象語言否具備的特性。今天這一節會着重的講解Kotlin的繼承
與Java
的不一樣處和Kotlin
獨有的特色。git
面向對象的三大特徵:
封裝
、繼承
、多態
github
因爲面向對象的三大特徵太過於普通,並且這並非Kotlin
中特有的知識。在這裏就很少作描述。編程
在
Kotlin
中,繼承這個特性除了定義關鍵字
,以及全部的父類和Java
語言不通以外,其餘的其實無太大的差異。不過既然寫到了這裏,仍是從始至終的寫完這個特性,若是您有Java
的基礎,您能夠當複習一遍。編輯器
Any
)在
Kotlin
中,說有的類都是繼承與Any
類,這是這個沒有父類型的類。即當咱們定義各種時,它默認是繼承與Any
這個超類的ide
例:函數
class Demo // 這裏定義了一個Demo類,即這個類默認是繼承與超類的。
複製代碼
由於Any
這個類只是給咱們提供了equals()
、hashCode()
、toString()
這三個方法。咱們能夠看看Any
這個類的源碼實現:post
package kotlin
/**
* The root of the Kotlin class hierarchy. Every Kotlin class has [Any] as a superclass.
* 看這個源碼註釋:意思是任何一個Kotlin的類都繼承與這個[Any]類
*/
public open class Any {
// 比較: 在平時的使用中常常用到的equals()函數的源碼就在這裏額
public open operator fun equals(other: Any?): Boolean
// hashCode()方法:其做用是返回該對象的哈希值
public open fun hashCode(): Int
// toString()方法
public open fun toString(): String
}
複製代碼
從源碼能夠咱們看出,它直接屬於kotlin
這個包下。而且只定義了上面所示的三個方法。或許你具備Java
的編程經驗。在咱們熟知的Java
中,全部的類默認都是繼承與Object
類型的。而Object
這個類除了比Any
多了幾個方法與屬性外,沒有太大的區別。不過他們並非同一個類。這裏就很少種講解了....學習
從上面源碼中所產生的疑惑:類與函數前面都加上了open
這個修飾符。那麼這個修飾符的做用是什麼呢?
其實咱們分析能夠得出:既然Any
類是全部類的父類,那麼咱們本身要定義一個繼承類,跟着Any
類的語法與結構就能定義一個繼承類。故而,open
修飾符是咱們定義繼承類的修飾符this
定義繼承類的關鍵字爲:
open
。不論是類、仍是成員都須要使用open
關鍵字。spa定義格式爲:
open class 類名{ ... open var/val 屬性名 = 屬性值 ... open fun 函數名() ... } 複製代碼
例:這裏定義一個繼承類Demo
,並實現兩個屬性與方法,而且定義一個DemoTest
去繼承自Demo
open class Demo{
open var num = 3
open fun foo() = "foo"
open fun bar() = "bar"
}
class DemoTest : Demo(){
// 這裏值得注意的是:Kotlin使用繼承是使用`:`符號,而Java是使用extends關鍵字
}
fun main(args: Array<String>) {
println(DemoTest().num)
DemoTest().foo()
DemoTest().bar()
}
複製代碼
輸出結果爲:
3
foo
bar
複製代碼
分析:從上面的代碼能夠看出,DemoTest
類只是繼承了Demo
類,並無實現任何的代碼結構。同樣可使用Demo
類中的屬性與函數。這就是繼承的好處。
在上一篇文章中,講解到了
Kotlin
類,能夠有一個主構造函數,或者多個輔助函數。或者沒有構造函數的狀況。若是您對Kotlin
的構造函數還不瞭解的狀況,請閱讀個人上一篇文章Kotlin——中級篇(一):類(class)詳解。
這裏當實現類無主構造函數,和存在主構造函數的狀況。
當實現類無主構造函數時,則每一個輔助構造函數必須使用
super
關鍵字初始化基類型,或者委託給另外一個構造函數。 請注意,在這種狀況下,不一樣的輔助構造函數能夠調用基類型的不一樣構造函數
例:這裏舉例在Android
中常見的自定義View實現,咱們熟知,當咱們指定一個組件是,通常實現繼承類(基類型)的三個構造函數。
class MyView : View(){
constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int)
: super(context, attrs, defStyleAttr)
}
複製代碼
能夠看出,當實現類無主構造函數時,分別使用了super()
去實現了基類的三個構造函數。
當存在主構造函數時,主構造函數通常實現基類型中參數最多的構造函數,參數少的構造函數則用
this
關鍵字引用便可了。這點在Kotlin——中級篇(一):類(class)詳解這篇文章是講解到的。
例:一樣以自定義組件爲例子
class MyView(context: Context?, attrs: AttributeSet?, defStyleAttr: Int)
: View(context, attrs, defStyleAttr) {
constructor(context: Context?) : this(context,null,0)
constructor(context: Context?,attrs: AttributeSet?) : this(context,attrs,0)
}
複製代碼
在
Kotlin
中關於函數的重載
與重寫
,和Java
中是幾乎是同樣的,可是這裏仍是舉例來講明一下。
2.3.一、重寫函數中的兩點特殊用法
不論是Java
仍是Kotlin
,重寫基類型裏面的方法,則稱爲重寫,或者是覆蓋基類型方法。不過這裏介紹兩點Kotlin
一點特殊的地方
- 當基類中的函數,沒有用
open
修飾符修飾的時候,實現類中出現的函數的函數名不能與基類中沒有用open
修飾符修飾的函數的函數名相同,無論實現類中的該函數有無override
修飾符修飾。讀着有點繞,直接看例子你就明白了。
例:
open class Demo{
fun test(){} // 注意,這個函數沒有用open修飾符修飾
}
class DemoTest : Demo(){
// 這裏聲明一個和基類型無open修飾符修飾的函數,且函數名一致的函數
// fun test(){} 編輯器直接報紅,根本沒法運行程序
// override fun test(){} 一樣報紅
}
複製代碼
- 當一個類不是用
open
修飾符修飾時,這個類默認是final
的。即:
class A{}
等價於
final class A{} // 注意,則的`final`修飾符在編輯器中是灰色的,由於Kotlin中默認的類默認是final的
複製代碼
那麼當一個基類去繼承另一個基類時,第二個基類不想去覆蓋掉第一個基類的方法時,第二個基類的該方法使用final
修飾符修飾。
例:
open class A{
open fun foo(){}
}
// B這個類繼承類A,而且類B一樣使用open修飾符修飾了的
open class B : A(){
// 這裏使用final修飾符修飾該方法,禁止覆蓋掉類A的foo()函數
final override fun foo(){}
}
複製代碼
2.3.二、方法重載
在文章的開頭提到了
多態
這個特性,方法的重載其實主要體如今這個地方。即函數名相同,函數的參數不一樣的狀況。這一點和Java
是相同的
這一點在繼承類中一樣有效:
例:
open class Demo{
open fun foo() = "foo"
}
class DemoTest : Demo(){
fun foo(str: String) : String{
return str
}
override fun foo(): String {
return super.foo()
}
}
fun main(args: Array<String>) {
println(DemoTest().foo())
DemoTest().foo("foo的重載函數")
}
複製代碼
輸出結果爲:
foo
foo的重載函數
複製代碼
- 重寫屬性和重寫方法其實大體是相同的,可是屬性不能被重載。
- 重寫屬性即指:在基類中聲明的屬性,而後在其基類的實現類中重寫該屬性,該屬性必須以
override
關鍵字修飾,而且其屬性具備和基類中屬性同樣的類型。且能夠重寫該屬性的值(Getter
)
例:
open class Demo{
open var num = 3
}
class DemoTest : Demo(){
override var num: Int = 10
}
複製代碼
2.4.一、重寫屬性中,val與var的區別
這裏能夠看出重寫了num
這個屬性,而且爲這個屬性重寫了其值爲10
可是,還有一點值得咱們注意:當基類中屬性的變量修飾符爲val
的使用,其實現類能夠用重寫屬性能夠用var
去修飾。反之則不能。
例:
open class Demo{
open val valStr = "我是用val修飾的屬性"
}
class DemoTest : Demo(){
/*
* 這裏用val、或者var重寫都是能夠的。
* 不過當用val修飾的時候不能有setter()函數,編輯器直接會報紅的
*/
// override val valStr: String
// get() = super.valStr
// override var valStr: String = ""
// get() = super.valStr
// override val valStr: String = ""
override var valStr: String = "abc"
set(value){field = value}
}
fun main(arge: Array<String>>){
println(DemoTest().valStr)
val demo = DemoTest()
demo.valStr = "1212121212"
println(demo.valStr)
}
複製代碼
輸出結果爲:
abc
1212121212
複製代碼
2.4.二、Getter()函數慎用super關鍵字
在這裏值得注意的是,在實際的項目中在重寫屬性的時候不用get() = super.xxx
,由於這樣的話,無論你是否從新爲該屬性賦了新值,仍是支持setter()
,在使用的時候都調用的是基類中的屬性值。
例: 繼上面中的例子
class DemoTest : Demo(){
/*
* 這裏介紹重寫屬性是,getter()函數中使用`super`關鍵字的狀況
*/
override var valStr: String = "abc"、
get() = super.valStr
set(value){field = value}
}
fun main(arge: Array<String>>){
println(DemoTest().valStr)
val demo = DemoTest()
demo.valStr = "1212121212"
println(demo.valStr)
}
複製代碼
輸出結果爲:
我是用val修飾的屬性
我是用val修飾的屬性
複製代碼
切記:重寫屬性的時候慎用super
關鍵字。否則就是上面例子的效果
2.4.三、在主構造函數中重寫
這一點和其實在
接口類
的文章中講解過了,不清楚的能夠去參見Kotlin——中級篇(五):枚舉類(Enum)、接口類(Interface)詳解。
例:基類仍是上面的例子
class DemoTest2(override var num: Int, override val valStr: String) : Demo()
fun main(args: Array<String>){
val demo2 = DemoTest2(1,"構造函數中重寫")
println("num = ${demo2.num} \t valStr = ${demo2.valStr}")
}
複製代碼
輸出結果爲:
num = 1 valStr = 構造函數中重寫
複製代碼
這裏的覆蓋規則,是指實現類繼承了一個基類,而且實現了一個接口類,當個人基類中的方法、屬性和接口類中的函數重名的狀況下,怎樣去區分實現類到底實現哪個中的屬性或屬性。 這一點和一個類同時實現兩個接口類,而兩個接口都用一樣的屬性或者函數的時候是同樣的。在
接口類
這篇文章中已經講解過,您能夠參見Kotlin——中級篇(五):枚舉類(Enum)、接口類(Interface)詳解。
例:
open class A{
open fun test1(){ println("基類A中的函數test1()") }
open fun test2(){println("基類A中的函數test2()")}
}
interface B{
fun test1(){ println("接口類B中的函數test1()") }
fun test2(){println("接口類B中的函數test2()")}
}
class C : A(),B{
override fun test1() {
super<A>.test1()
super<B>.test1()
}
override fun test2() {
super<A>.test2()
super<B>.test2()
}
}
複製代碼
對於Kotlin
中繼承類這一個知識點,在項目中用到的地方是很常見的。當你認真的學習完上面的內容,我相信你能夠能很輕易的用於項目中,不過對一個類來講,繼承的代價較高,當實現一個功能沒必要用到太多的集成屬性的時候,能夠用對象表達式
這一個高級功能去替代掉繼承。
若是你有過其餘面嚮對象語言的編程經驗的話,你只要掌握其關鍵字、屬性/函數重寫、以及覆蓋規則
這三三個知識點就能夠了。
若是各位大佬看了以後感受還闊以,就請各位大佬隨便star
一下,您的關注是我最大的動力。
個人我的博客:Jetictors
Github:Jteictors