Kotlin入門(Android開發)基礎知識彙總(四)之類和對象---完結篇

1.類的構造

1.1類的簡單定義

//java
public class MainActivity extends AppCompatActivity {
    ... 
}

//Kotlin
class MainActivity : AppCompatActivity() {
    ... 
}

Kotlin對類的寫法有以下特點:

<1>省略了關鍵字public,緣於它默認就是開放的;

<2>用冒號“:”代替extends,表示繼承關係;

<3>進行繼承時,父類後面多了括號()

class Animal {
    //類的初始化函數init
    init {
        //Kotlin使用println代替Java的System.out.println
        println("Animal:這是個動物類")
    }
}

var animal = Animal()//類的實例創建,忽略new關鍵字

1.2 類的構造函數

//如果主構函數沒有帶@符號的註解說明,constructor就可以省略
//class AnimalMain (context:Context, name:String) {
class AnimalMain constructor(context:Context, name:String) {
    init {
        context.toast("這是隻$name")
    }
}
//含二級構造函數
class AnimalMain constructor(context:Context, name:String) {
    init {
        context.toast("這是隻$name")
    }
    constructor(context:Context, name:String, sex:Int) : this(context,
    name) {
    var sexName:String = if(sex==0) "公" else "母"
    context.toast("這隻${name}是${sexName}的")
    }
}

<1>二級構造函數沒有函數名稱,只用關鍵字constructor表示這是一個構造函數;

<2>二級構造函數需要調用主構函數。

//構造函數調用
btn_class_main.setOnClickListener {
    setAnimalInfo()
    when (count%2) {
        0 -> { var animal = AnimalMain(this, animalName) }
        else -> { var animal = AnimalMain(this, animalName, animalSex) }
    }
}

上述二級構造函數存在一個問題,會調用到主構函數的toast,爲了只調用對應的構造函數,改寫成如下形式:

class AnimalSeparate {
    constructor(context:Context, name:String) {
        context.toast("這是隻$name")
    }
    constructor(context: Context, name:String, sex:Int) {
        var sexName:String = if(sex==0) "公" else "母"
        context.toast("這隻${name}是${sexName}的")
    }
}

1.3 帶默認參數的構造函數

class AnimalDefault (context: Context, name:String, sex:Int = 0) {
    init {
        var sexName:String = if(sex==0) "公" else "母"
        context.toast("這隻${name}是${sexName}的")
    }
}

var animal = AnimalDefault(this, animalName)//調用,默認sex爲“公”

注:上述類如果想要Java類識別默認參數,需要加入“@JvmOverloads”註解:

//因爲加入了註解標誌@,需要補上constructior關鍵字
class AnimalDefault @JvmOverloads constructor(context: Context, name:St
ring, sex:Int = 0) {
    init {
        var sexName:String = if(sex==0) "公" else "母"
        context.toast("這隻${name}是${sexName}的")
    }
}

AnimalDefault animal = new AnimalDefault(this, animalName);//java 代碼調用

2.類的成員

2.1成員屬性

class WildAnimal (var name:String, val sex) {
}

animal.name //相當於Java的get方法

成員變量需要var或val聲明,調用只需對象.成員變量即可。

2.2 成員方法

class WildAnimalFunction (var name:String, val sex:Int = 0) {
    var sexName:String
    init {
        sexName = if(sex==0) "公" else "母"
    }

    //成員方法
    fun getDesc(tag:String):String {
        return "歡迎來到$tag:這隻${name}是${sexName}的"
    }
}

animal.getDesc("動物園")//調用

2.3 伴生對象

class WildAnimalCompanion (var name:String, val sex:Int = 0) {
    ...
    //在類加載時就運行伴生對象的代碼塊,其作用相當於Java裏面的static{...}代碼塊
    //在關鍵字companion表示伴隨,object表示對象
    companion object WildAnimal{
        fun judgeSex(sexName:String):Int {
            var sex:Int = when (sexName) {
                "公","雄" -> 0
                "母","雌" -> 1
                else -> -1
            }
            return sex
        }
    }
}

WildAnimalCompanion.WildAnimal.judgeSex(sexName)//調用
WildAnimalCompanion.judgeSex(sexName)//WildAnimal可省略

2.4 靜態屬性

companion object WildAnimal{
    
    //靜態常量的值是不可變的,所以要使用關鍵字val修飾
    val MALE = 0
    val FEMALE = 1
    val UNKNOWN = -1
    fun judgeSex(sexName:String):Int {
        var sex:Int = when (sexName) {
            "公","雄" -> MALE
            "母","雌" -> FEMALE
            else -> UNKNOWN
        }
    return sex
    }
}

WildAnimalCompanion.MALE//調用

3 類的繼承

3.1開放性修飾詞

開放性修飾詞 說明
public 對所有人開放。Kotlin的類、函數、變量不加開放性修飾詞的話,默認就是public類型
internal 只對本模塊開放。這是Kotlin新增的關鍵字,對於app開發來說,本模塊便指app自身
protected 只對自己和子類開放
privated 只對自己開放,即私有

 

3.2類的繼承

Kotlin的類默認是不能繼承的(即final類型),如果需要繼承某類,該父類就應當聲明爲open類型。

open class Bird (var name:String, val sex:Int = MALE) {
    
    //需要被繼承的方法也需要加上open
    open protected fun getSexName(sex:Int):String {
        return if(sex==MALE) "公" else "母"
    }
    
    fun getDesc(tag:String):String {
        return "歡迎來到$tag這隻${name}是${sexName}的"
    }

    companion object BirdStatic{
        val MALE = 0
        val FEMALE = 1
        val UNKNOWN = -1
        fun judgeSex(sexName:String):Int {
            var sex:Int = when (sexName) {
                "公","雄" -> MALE
                "母","雌" -> FEMALE
                else -> UNKNOWN
            }
        return sex
        }
    }
}


//繼承Bird的子類Duck
class Duck(name:String="鴨子", sex:Int = Bird.MALE) : Bird(name, sex) {
}

//activity調用
btn_class_duck.setOnClickListener {
    var sexBird = if (count++%3==0) Bird.MALE else Bird.FEMALE
    var duck = Duck(sex=sexBird)
    tv_class_inherit.text = duck.getDesc("鳥語林")
}

 繼承父類protected方法,標準寫法是“override protected”,然而protected可忽略。示例

class Ostrich(name:String="鴕鳥", sex:Int = Bird.MALE) : Bird(name, sex) {
    
    //protected的方法繼承後可見性可升級爲public,但不能降級爲private
    override public fun getSexName(sex:Int):String {
        return if(sex==MALE) "雌" else "雄"
    }
    
}

3.3 抽象類

abstract class Chicken(name:String, sex:Int, var voice:String) : Bird(n
ame, sex) {
    ...
    abstract fun callOut(times:Int):String
}

子類的構造函數,原來的輸入參數不用加var或val,新增的輸入參數必須加入var或val,因爲抽象類不能直接使用,所以構造函數不必給默認參數賦值,抽象方法必須在子類重寫。

class Cock(name:String="雞", sex:Int = Bird.MALE, voice:String="喔喔喔") : Chicken(name, sex, voice) {
    override fun callOut(times: Int): String {
        var count = when {
            times<=0 -> 0
            times>=10 -> 9
            else -> times
        } 
        return "$sexName$name${voice}叫了${numberArray[count]}聲,原來它在報曉。"   
    }
}

tv_class_inherit.text = Cock().callOut(count++%10)//activity調用

3.4 接口

//接口不能帶構造函數
interface Behavior {
    
    //接口內部的方法默認就是抽象的,open和abstract關鍵字可忽略
    open abstract fun fly():String
    fun swim():String
    
    //kotlin接口與Java接口不一樣,允許實現方法
    //該方法默認是open類型,接口的方法默認都是open類型
    fun run():String {
        return "大多數鳥兒跑得並不像樣"
    }

     //Kotlin的接口允許聲明抽象屬性,實現該接口的類必須重載該屬性
     //與接口內部方法一樣,抽象屬性前面的open和abstract也可以忽略
    var skilledSports:String
}
class Goose(name:String="鵝", sex:Int = Bird.MALE) : Bird(name, sex), Behavior {

    override fun fly():String {
        return "鵝能飛一點點,但飛不高,飛不遠"
    }    
    
    override fun swim():String {
        return "鵝,鵝,鵝,曲項向天歌,白毛浮綠水,紅掌撥清波"
    }
    
    //由於接口實現了run方法,所以此處可以不用實現該方法,當然也可以實現
    override fun run():String {
        //super用來調用父類的屬性或方法,由於kotlin的接口允許實現方法,因此super所指的對象也可以是interface
        return super.run()
    }
    
    //重載接口的抽象屬性
    override var skilledSports:String = "游泳"
}

其他類實現接口時,跟類繼承一樣把接口名稱放在冒號後面,如果存在2個以上的接口或者既有父類也有接口,此時中間用逗號隔開。

btn_interface_behavior.setOnClickListener {
    tv_class_inherit.text = when (count++%3) {
        0 -> Goose().fly()
        1 -> Goose().swim()
        else -> Goose().run()
    }
}

3.5 接口代理

一個類先聲明繼承自某個接口,但並不直接實現該接口的方法,而是把已經實現該接口的代理類作爲參數傳給前面的類,相當於告訴前面的類:“該接口的方法我已經代替你實現,你直接拿去用便是。” 這樣做的好處是,輸入參數可以按照具體的業務場景傳送相應的代理類。

示例:

//飛禽的行爲類
class BehaviorFly : Behavior {
    override fun fly():String {
        return "翱翔天空"
    }
    override fun swim():String {
        return "落水鳳凰不如雞"
    }
    override fun run():String {
        return "能飛幹嘛還要走"
    }
    override var skilledSports:String = "飛翔"
}

//水禽的行爲類
class BehaviorSwim : Behavior {

    override fun fly():String {
        return "看情況,大雁能展翅高飛,企鵝卻欲飛還休"
    }

    override fun swim():String {
        return "怡然戲水"
    }

    override fun run():String {
        return "趕鴨子上樹"
    }

    override var skilledSports:String = "游泳"
}

//走禽的行爲類
class BehaviorRun : Behavior {

    override fun fly():String {
        return "飛不起來"
    }

    override fun swim():String {
        return "望洋興嘆"
    }

    override fun run():String {
        return super.run()
    }

    override var skilledSports:String = "奔跑"
}

接着定義一個引用了代理類的野禽基類,通過關鍵字by表示該接口將由入參中的代理類實現。

//如果by的對象是個類,將編譯報錯
class WildFowl(name:String, sex:Int=MALE, behavior:Behavior) : Bird(name, sex), Behavior by behavior {
}

btn_delegate_behavior.setOnClickListener {
    var fowl = when (count++%6) {
        0 -> WildFowl("老鷹", Bird.MALE, BehaviorFly())
        1 -> WildFowl("鳳凰", behavior=BehaviorFly())
        2 -> WildFowl("大雁", Bird.FEMALE, BehaviorSwim())
        3 -> WildFowl("企鵝", behavior=BehaviorSwim())
        4 -> WildFowl("鴕鳥", Bird.MALE, BehaviorRun())
        else -> WildFowl("燕子", behavior=BehaviorRun())
    }

    var action = when (count%11) {
        in 0..3 -> fowl.fly()
        4,7,10 -> fowl.swim()
        else -> fowl.run()
    }

    tv_class_inherit.text = "${fowl.name}$action"
}

4 幾種特殊類

4.1嵌套類

在類的內部定義新類,這個新類叫作內部類。

class Tree(var treeName:String) {
    class Flower (var flowerName:String) {
        fun getName():String {
            return "這是一朵$flowerName"
            //普通的嵌套類不能訪問外部類的成員,如treeName
        }
       
    }
}

btn_class_nest.setOnClickListener {

    val peachBlossom = Tree.Flower("桃花");
    tv_class_secret.text = peachBlossom.getName()

}

4.2 內部類

嵌套類加上inner前綴,就成爲內部類

class Tree(var treeName:String) {

    ...

    inner class Fruit (var fruitName:String) {
        fun getName():String {
            
            //只有聲明瞭內部類,才能訪問外部類的成員
            return "這是${treeName}長出來的$fruitName"
        }
    }

}

4.3 枚舉類

將關鍵字enum 修飾class就變成枚舉類

enum class SeasonType {
    SPRING, SUMMER, AUTUMN, WINTER
}

枚舉類屬性

ordinal屬性用於獲取該枚舉值的序號,name屬性用於獲取該枚舉值的名稱。

枚舉類如果存在構造函數,枚舉變量也必須調用對應的構造函數。

enum class SeasonName (val seasonName:String) {
    SPRING("春天"),
    SUMMER("夏天"),
    AUTUMN("秋天"),
    WINTER("冬天")
}

//調用
SeasonType.SPRING.ordinal
SeasonType.SPRING.name

SeasonName.SPRING.seasonName//調用構造函數的名稱

4.4 密封類

密封類像是一種更嚴格的枚舉類,它內部有且僅有的實例對象,所以是一個有限的自身實例集合。需要在該類的class前面添加sealed作爲標誌

sealed class SeasonSealed {
    //密封類內部的每個嵌套類都必須繼承該類
    class Spring (var name:String) : SeasonSealed()
    class Summer (var name:String) : SeasonSealed()
    class Autumn (var name:String) : SeasonSealed()
    class Winter (var name:String) : SeasonSealed()
}

SeasonSealed.Spring("春天")

密封類確保條件分支覆蓋了所有的枚舉類型,因此不再需要else分支

tv_class_secret.text = when (season) {
    is SeasonSealed.Spring -> season.name
    is SeasonSealed.Summer -> season.name
    is SeasonSealed.Autumn -> season.name
    is SeasonSealed.Winter -> season.name
}

4.5 數據類

在class面前添加修飾詞data,並聲明擁有完整輸入參數的構造函數,那麼該類稱爲數據類。數據類有以下功能:

<1>自動聲明與構造函數入參同名的屬性字段;

<2>自動實現每個屬性字段的get/set方法;

<3>自動提供equals方法,用於比較兩個數據對象是否相等;

<4>自動提供copy()方法,允許完整複製某個數據對象,也可在複製後單獨修改某幾個字段的值;

<5>自動提供toString()方法,用於打印數據對象中保存的所有字段值

示例:

//數據類必須有主構函數,且至少有一個輸入參數
//輸入參數前要添加關鍵字var或val
//數據類不能是基類也不能是子類,不能是抽象類,也不是內部類,更不是密封類
data class Plant(var name:String, var stem:String, var leaf:String, var flower:String, var fruit:String, var seed:String) {
}
var lotus = Plant("蓮", "蓮藕", "蓮葉", "蓮花", "蓮蓬", "蓮子")
//數據類的copy()方法不帶參數,表示複製一模一樣的對象
var lotus2 = lotus.copy()
btn_class_data.setOnClickListener {
    lotus2 = when (count++%2) {
        
        0 -> lotus.copy(flower="􁞚􁜰")
        else -> lotus.copy(flower="􁞰􁜰")
    }
    
    var result = if (lotus2.equals(lotus)) "相等" else "不等"
    
}

4.6 模板類

舉例說明

//在類名後面添加“<T>”,表示這是一個模板類
class River<T> (var name:String, var length:T) {
    fun getInfo():String {
        var unit:String = when (length) {
        is String -> "􁔂"
        //Int,Long,Float,Double都是數字類型Number
        is Number -> "m"
        else -> ""
    }
    return "${name}的長度是$length$unit。"
    }
}
btn_class_generic.setOnClickListener {
    var river = when (count++%4) {
        
        //模板類(泛型類)聲明對象時,要在模板類的類名後面加上“<參數類型>”
        0 -> River<Int>("小溪", 100)
        //如果編譯器根據輸入參數就能知曉參數類型,也可直接忽略“<參數類型>”
        1 -> River("瀑布", 99.9f)
        ....
    }
    ...
}

備註:第1到第4篇kotlin基礎知識彙總全部摘自《Kotlin從零到精通(Android開發)》一書。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章