天天看點

Kotlin學習日志(五)類與對象

一、類的構造

1.1 類的簡單定義

首先來看看在Android中Java的MainActivity

public class MainActivity extends AppCompatActivity {
  ...
}      

再看看Kotlin中的MainActivity

class MainActivity : AppCompatActivity() {
  ...
}      

通過上述的代碼比較,Kotlin對類的寫法與Java之間有以下幾點差別:

(1)Kotlin省略了關鍵字public,因為Kotlin預設類是開放的,是以不需要這個關鍵字。

(2)Kotlin用冒号“:”代替extends,也就是通過冒号表示繼承關系。

(3)Kotlin進行繼承時,父類後面多了括号“()”。

然後我們自己建立名為Animal的Kotlin類

步驟:

滑鼠右鍵你的包名→New→Kotlin File/Class→建立的檔案類型選擇Class→OK(建立完成)

Kotlin學習日志(五)類與對象
Kotlin學習日志(五)類與對象
Kotlin學習日志(五)類與對象

然後你就會看到這樣的一個圖

Kotlin學習日志(五)類與對象

現在開始編寫代碼:

package com.llw.kotlinstart

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

現在這個類已經建立好了,并且有了初始化函數,我們在MainActivity.kt中來執行個體化這個類,代碼如下:

activity_main.xml代碼

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:gravity="center_horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <TextView
        android:textColor="#000"
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <TextView
        android:textColor="#000"
        android:id="@+id/tv_result"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>

    <LinearLayout
        android:gravity="center"
        android:layout_marginTop="20dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <Button
            android:id="@+id/btn_test"
            android:text="Test"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
    </LinearLayout>


</LinearLayout>      

MainActivity.kt代碼

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*

class MainActivity : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_test.setOnClickListener {
            //因為根據等号後面的構造函數已經明确知道這是個Animal的執行個體
            //是以聲明對象時可以不用指定它的類型
            var animal = Animal()
            tv_result.text = "簡單類的初始化結果見日志"
        }

    }



}      

運作效果圖如下:

Kotlin學習日志(五)類與對象
Kotlin學習日志(五)類與對象

經過這一番操作,我們再與Java對比一下差別:

(1)Kotlin對類進行初始化的函數名稱叫init,不像Java那樣把雷鳴作為構造函數的名稱。

(2)Kotlin列印日志使用類似C語言的println方法,而非Java的System.out.println

(3)Kotlin建立執行個體時省略了關鍵字new。

這裡面,初始化函數init看似是Kotlin對類的構造函數,但它隻是構造函數的一部分,并不完整,因為沒有定義輸入參數,那麼怎麼定義呢?誰來定義呢?

1.2 類的構造函數

入參的類定義代碼如下:

//如果主構造函數沒有帶@符号的注解說明,類名後面的constructor就可以省略
    class AnimalMain constructor(context:Context,name:String){
    //class AnimalMain (context:Context,name:String){
        init {
            context.toast("這是頭$name")
        }
    }      

一個類可能有多個構造函數,Java可以通過覆寫帶不同參數的構造函數來實作,那麼Kotlin已經在類名後面指明了固定數量的入參,又該如何表示擁有其他參數的構造函數呢?針對這個問題,Kotlin引入了主構造函數與二級構造函數的概念,之前的代碼示範的是主構造函數,分為兩部分,跟在類名後面的參數是主構造函數的入參,同時init方法是主構造函數的内部代碼,至于二級構造函數,則可以在類内部直接書寫完整的函數表示式,建立一個名為AnimalMain的類,代碼如下:

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)二級構造函數需要調用主構造函數。“this(context,name)”這句代碼在Java中要以“super(context,name)”的形式寫在函數體内部,在Kotlin中則以冒号開頭補充到輸入參數後面,這意味着二級構造函數實際上是從主構造函數派生出來的,也可以看作二級函數的傳回值是主構造函數。

由此看來,二級構造函數從屬于主構造函數,如果使用二級構造函數聲明該類的執行個體,系統就會先調用主構造函數的init代碼,再調用二級構造函數的自身代碼,現在若想聲明AnimalMain類的執行個體,既可通過主構造函數,也可通過二級構造函數,代碼如下:

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*

class MainActivity : AppCompatActivity() {


    var animalName:String = ""
    var animalSex:Int = 0
    var count:Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)


        btn_test.setOnClickListener {
            setAnimalInfo()
            when(count%2){
                0 -> { var animal = AnimalMain(this,animalName) }
                else -> { var animal = AnimalMain(this,animalName,animalSex)  }
            }
            count++
        }

    }

    fun setAnimalInfo() {
        animalName = "牛"
        animalSex = 0
    }


}      

上面代碼在運作過程中,通過二級構造函數聲明執行個體有一個問題,就是toast會彈窗兩次,因為主構造函數的init方法已經彈窗,然後二級構造函數自身再次彈窗,那能不能不調用主構造函數呢?為了解決該問題,Kotlin設定了主構造函數時不是必需的,也就是說類可以把幾個構造函數都放在類内部定義,進而都變成二級構造函數,如此就去掉了主構造函數,為了直覺,重建立名為一個AnimalSeparate的類,代碼如下

package com.llw.kotlinstart

import android.content.Context
import org.jetbrains.anko.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 帶預設參數的構造函數

說到預設參數,不知道你有沒有想起之前的帶預設參數的函數呢?上面的代碼中,兩個構造函數之間隻有一個輸入參數的差別,是以完全可以把二者合二為一,變成一個帶預設參數的主構造函數,新的主構造函數既能輸入兩個參數,又能輸入三個參數,新建立一個類AnimalDefault,代碼如下:

package com.llw.kotlinstart

import android.content.Context
import org.jetbrains.anko.toast

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

運作效果類似,但是代碼更加的簡潔了。

二、類的成員

2.1成員屬性

建立一個新的類WildAnimal,然後在構造函數中放兩個參數,代碼如下:

class WildAnimal(name:String,sex:Int = 0) {
    
}      

然後我們再聲明對應的屬性字段,用于儲存入參的數值,加入按照Java的編碼思路,下面的代碼應該是這樣的。

class WildAnimal(name: String, sex: Int = 0) {
    var name: String // 表示動物名稱可以修改
    val sex: Int //表示動物性别不能修改

    init {
        this.name = name
        this.sex = sex
    }
}      

這上面的寫法從Java的角度來看倒是沒有問題,但如果時Kotlin呢,代碼備援了,

(1)屬性字段跟構造函數的入參,二者名稱一樣,變量類型也一樣。

(2)初始化函數中的屬性字段指派,為了差別同名的屬性和入參,特意給屬性字段添加了this。

那麼Kotlin如何精簡這個類的代碼呢?代碼如下:

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

你沒有看錯,就是這樣,接下來使用一下吧。

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*

class MainActivity : AppCompatActivity() {


    var animalName: String = ""
    var animalSex: Int = 0
    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_test.setOnClickListener {
            setAnimalInfo()
            var animal = when (count % 2) {
                0 -> {
                    WildAnimal(animalName)
                }
                else -> {
                    WildAnimal(animalName, animalSex)
                }
            }
            count++

            tv_result.text = "這頭${animal.name}是${if (animal.sex == 0) "公" else "母"}的"
        }

    }

    fun setAnimalInfo() {
        animalName = "牛"
        animalSex = 1
    }


}      

再看看Java代碼中怎麼做的

package com.llw.kotlinstart;

public class WildAnimal {
    private String name;
    private String sex;

    public WildAnimal(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }
}      

很熟悉吧,因為基本上每一個實體都離不開這一步,

對比一下:

(1)備援的同名屬性聲明語句。

(2)備援的同名屬性指派語句。

(3)備援的屬性擷取方法與設定方法。

Kotlin的代碼真的精簡了很多,鳥槍換炮,

如果某個字段并非入參的同名屬性,就需要在類内部顯示聲明該屬性字段,例如,前面WildAnimal類的性别隻是一個整型的類型字段,而界面上展示的是性别的中文名稱,是以應當給該類補充一個性别名稱的屬性字段,這樣每次通路sexName字段即可獲得該動物的性别名稱,建立一個名為WildAnimalMember的類,代碼如下:

package com.llw.kotlinstart

class WildAnimalMember (val name:String,val sex:Int = 0) {
    //非空的成員屬性必須在聲明時指派或者在構造函數中指派,否則編譯器會報錯
    var sexName:String
    init {
        sexName = if(sex == 0) "公" else "母"
    }
}      

然後再看一下怎麼調用這個類:

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.Exception
import java.text.SimpleDateFormat
import java.util.*

class MainActivity : AppCompatActivity() {


    var animalName: String = ""
    var animalSex: Int = 0
    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_test.setOnClickListener {
            setAnimalInfo()
            var animal = when (count % 2) {
                0 -> {
                    WildAnimalMember(animalName)
                }
                else -> {
                    WildAnimalMember(animalName, animalSex)
                }
            }
            count++

            tv_result.text = "這頭${animal.name}是${animal.sexName}的"
        }

    }

    fun setAnimalInfo() {
        animalName = "牛"
        animalSex = 1
    }


}      

2.2 成員方法

類的成員除了成員屬性還有成員方法,在類内部定義成員方法的過程和普通函數定義比較類似。下面增加一個擷取動物描述資訊的成員方法getDesc(),新建立一個名為WildAnimalFunction的類

package com.llw.kotlinstart

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}的"
    }
}      

然後我們在MainActivity.kt中調用這個類的方法

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.WildAnimalFunction
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {


    var animalName: String = ""
    var animalSex: Int = 0
    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_test.setOnClickListener {
            setAnimalInfo()
            var animal = when(count%2){
                0 -> WildAnimalFunction(
                    animalName
                )
                else -> WildAnimalFunction(
                    animalName,
                    animalSex
                )
            }
            tv_result.text = animal.getDesc("動物園")
            count++
        }

    }

    fun setAnimalInfo() {
        animalName = "牛"
        animalSex = 1
    }


}      
Kotlin學習日志(五)類與對象
Kotlin學習日志(五)類與對象

2.3 伴生對象

伴生對象這個是在Kotlin中有的,Java中沒有,什麼是伴生對象呢,你可以把它了解為“影子”,把類當做一個人,這個人可以有很多房子,但是人隻有一個,影子也隻有一個。你也可以把伴生對象替換掉靜态成員的作用,但它比靜态成員的功能要強大。我們之前通過性别類型來獲得性别名稱,那麼反推呢,我們使用伴生對象來實作這一功能,新建立一個名為WildAnimalCompanion的類

package com.llw.kotlinstart.custom_class

class WildAnimalCompanion (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}的"
    }

    //關鍵字companion表示伴随,object表示對象,WildAnimal表示伴生對象的名稱
    companion object WildAnimal{
        fun judgeSex(sexName:String):Int{
            var sex:Int = when (sexName){
                "公","雄" -> 0
                "母","雌" -> 1
                else -> -1
            }
            return sex
        }
    }
}      

代碼應該沒有什麼好說的,一目了然,關鍵定義這個伴生對象和使用它,接下來看怎麼使用

代碼如下:

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.WildAnimalCompanion
import com.llw.kotlinstart.custom_class.WildAnimalFunction
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val sexArray:Array<String> = arrayOf("公","母","雄","雌")

        btn_test.setOnClickListener {
            var sexName:String = sexArray[count++%4]
            //伴生對象的WildAnimal名稱可以省略掉
            //tv_result.text = "\"$sexName\"對應的類型是${WildAnimalCompanion.WildAnimal.judgeSex(sexName)}"
            tv_result.text = "\"$sexName\"對應的類型是${WildAnimalCompanion.judgeSex(sexName)}"//雙引号 要加反斜杠  如同這樣 \"\"
        }


    }


}      
Kotlin學習日志(五)類與對象

這個是灰色的,我們省略掉也是可以的

Kotlin學習日志(五)類與對象
Kotlin學習日志(五)類與對象

有四種結果,我隻放兩個圖

2.4 靜态屬性

之前我們的伴生對象可以實作靜态函數,同樣也能實作靜态屬性,隻要在伴生對象内部增加幾個字段定義就行了,之前是用0和1表示動物的雄雌,接下來用整型常量MALE表示雄性的0,整型常量FEMALE表示雌性的1,建立一個名為WildAnimalConstant的類,代碼如下,

package com.llw.kotlinstart.custom_class

class WildAnimalConstant(var name: String, val sex: Int = MALE) {
    var sexName: String

    init {
        sexName = if (sex == MALE) "公" else "母"

    }

    fun getDesc(tag: String): String {
        return "歡迎來到$tag: 這隻${name}是${sexName}的。"
    }

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

然後再進行調用

val sexArray:Array<String> = arrayOf("公","母","雄","雌")

        btn_test.setOnClickListener {
            var sexName:String = sexArray[count++%4]
            //伴生對象的WildAnimal名稱可以省略掉
            //tv_result.text = "\"$sexName\"對應的類型是${WildAnimalCompanion.WildAnimal.judgeSex(sexName)}"
            tv_result.text = "\"$sexName\"對應的類型是${WildAnimalConstant.judgeSex(sexName)}"//雙引号 要加反斜杠  如同這樣 \"\"
        }      

改一下類名就可以了,運作效果和之前的是一樣的,隻不過程式裡面就可以通過WildAnimalConstant.MALE和WildAnimalConstant.FEMALE來判斷公母了,不像之前通過0和1這種毫無意義的值來判斷。

Kotlin的類成員分為執行個體成員與靜态成員,執行個體成員包括成員屬性和成員方法,其中與入參同名的成員屬性可以在構造函數中直接聲明,外部必須通過類的執行個體才能通路類的成員屬性和成員方法,類的靜态成員包括靜态屬性與靜态方法,它們都在類的伴生對象中定義,外部可以通過類名直接通路該類的靜态成員。

三、類的繼承

我們一開始就提到了類的繼承,如class MainActivity : AppCompatActivity(),這和Java是不一樣的,那麼Kotlin怎麼定義基類并由基類派生出子類呢?

3.1 開放性修飾符

之前我們寫了好多個WildAnimal類,Java和Kotlin關于類的繼承還有差別,比如Java中預設每個類都能被繼承,除非加了final關鍵字,而Kotlin剛好相反,它預設每個類都不能被繼承(PS:這不是搞我心态嗎!!!),這個時候要想讓一個類成為基類,就要把該類開放出來,于是就用到了開放性修飾符open(PS:敲黑闆,重點來了,哪個),示範代碼如下:

open class Animal(var name:String,val sex:Int = 0){

    }      

在Java中有幾個熟悉的關鍵字,public、protected、private,分别表示公開、隻對子類開放、私有。那麼在Kotlin中也給出了4個開放性修飾符。

開放性修飾符 說明
public 對所有人開放。Kotlin的類、函數、變量不加開放性修飾符的話,預設就是public類型
internal 隻對本子產品内部開放,這是Kotlin新增的關鍵字。
protected 隻對自己和子類開放
private 隻對自己開放、即私有

注意到這幾個修飾符與open一樣都加在類和函數前面,并且都包含“開放”的意思,乍看起來還真有點迷,到底open跟這4個開放性修飾符是什麼關系呢?其實很簡單,open不控制某個對象的通路權限,隻決定該對象能否繁衍開來,說白了,就是公告這個叼毛有沒有資格繁衍下一代,隻有頭戴open帽子的類,才允許作為基類派生出子類來,而頭戴open帽子的函數,表示它允許在子類中進行重寫,如果沒戴open帽子,該類就隻好打光棍了,函數沒戴open帽子的話,類的孩子就沒有辦法修改它。

至于那4個開放性修飾符,則是用來限定允許通路某對象的外部範圍,通俗地說,就是哪裡的帥哥可以跟這個美女搞對象,頭戴public的,表示全世界的帥哥都能跟她處對象,頭戴internal的,表示隻有本國的帥哥可以,頭戴protected的,表示自由本機關以及下屬機關的可以,頭戴private,表示自己本機關可以。

3.2 普通類繼承

建立一個Poultry類,代碼如下:

package com.llw.kotlinstart.custom_class

//Kotlin的類型預設是不能繼承的(即 final類型),如果需要繼承某類,該父類就應當聲明open類型
open class Poultry (var name:String,val sex:Int = MALE){
    //變量、方法、類預設都是public,是以一般都把public省略掉了
    var sexName:String
    init {
        sexName = getSexName(sex)
    }

    //私有的方法既不能被外部通路,也不能被子類繼承,是以open與private不能共存,否則編譯器會報錯
    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 UNKOWN = -1
        fun judgeSex(sexName:String):Int {
            var sex:Int = when (sexName){
                "公","雄" -> MALE
                "母","雌" -> FEMALE
                else -> UNKOWN
            }
            return sex
        }
    }
}      

然後我們再建立一個名為Pig的子類,繼承Poultry,代碼如下:

package com.llw.kotlinstart.custom_class

//注意父類Bird已經在構造函數聲明了屬性,故而子類Pig無須重複聲明屬性
//也就是說,子類的構造函數在輸入參數前面不需要再加val和var
class Pig(name:String="豬",sex: Int= MALE) : Poultry(name, sex){

}      

然後在Activity中調用:

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var sex:Int
        btn_test.setOnClickListener {
            var sexPoultry = if(count++%3==0) Poultry.MALE else Poultry.FEMALE
            var pig = Pig(sex = sexPoultry)
            tv_result.text = pig.getDesc("高老莊")
        }


    }


}      

運作效果圖如下:

Kotlin學習日志(五)類與對象
Kotlin學習日志(五)類與對象

然後再來定義一個小狗類 Dog的代碼

package com.llw.kotlinstart.custom_class

class Dog (name:String = "哈士奇",sex:Int = MALE):Poultry(name, sex){
    override public fun getSexName(sex: Int): String {
        return if(sex == MALE) "雄" else "雌"
    }

}      

然後在Activity中調用

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var sex:Int
        btn_test.setOnClickListener {
            var sexPoultry = if(count++%3==0) Poultry.MALE else Poultry.FEMALE
            var dog = Dog(sex = sexPoultry)
            tv_result.text = dog.getDesc("狗狗流浪記")
        }


    }


}      

運作效果圖如下:

Kotlin學習日志(五)類與對象
Kotlin學習日志(五)類與對象

3.3 抽象類

Kotlin中也存在與Java類似的抽象類,抽象類之是以存在,是因為其内部擁有被關鍵字abstract修飾的抽象方法。抽象方法沒有具體的函數體,故而外部無法直接聲明抽象類的執行個體,隻有在子類繼承時重寫方法,方可使用該子類正常聲明對象執行個體。

For Example ,雞屬于鳥類,可是公雞和母雞的叫聲是不一樣的,是以雞這個類的叫喚方法“callOut”發出什麼聲音并不确定,隻能先聲明為抽象方法,連帶着雞類“Chicken”也變成抽象類了。

現在定義一個抽象的Chicken類,代碼如下:

package com.llw.kotlinstart.custom_class
//子類的構造函數,原來的輸入參數不用加var和val,新增的輸入參數必須加var或者val
//因為抽象類不能直接使用,是以構造函數不必預設參數指派
abstract class Chicken (name:String,sex:Int,var voice:String):Poultry(name, sex){
    val numberArray:Array<String> = arrayOf("一","二","三","四","五","六","七")
    //抽象方法必須在子類進行重寫,是以可以省略關鍵字open,因為abstract方法預設就是open類型
    //open abstract fun callOut(times:Int):String
    abstract fun callOut(times:Int):String
}      

然後我們從Chicken類派生出公雞類Cock,指定攻擊的叫聲為“喔喔喔”,同時還要重寫callOut方法,明确公雞的叫喚行為。具體代碼如下:

package com.llw.kotlinstart.custom_class

class Cock(name:String="雞",sex:Int = Poultry.MALE,voice:String="喔喔喔"): Chicken(name, sex, voice) {
    override fun callOut(times: Int): String {
        var count = when {
            //when語句判斷大于和小于時,要把完整的判斷條件寫到每個分支中
            times <=0 ->0
            times >=7 -> 6
            else -> times
        }
        return "$sexName$name${voice}叫了${numberArray[count]}聲,這是在報曉"
    }
}      

再派生出公雞類Hen,指定攻擊的叫聲為“咯咯咯”,再重寫callOut方法

package com.llw.kotlinstart.custom_class

class Hen(name:String="雞",sex:Int = Poultry.FEMALE,voice:String="咯咯咯"): Chicken(name, sex, voice) {
    override fun callOut(times: Int): String {
        var count = when {
            //when語句判斷大于和小于時,要把完整的判斷條件寫到每個分支中
            times <=0 ->0
            times >=7 -> 6
            else -> times
        }
        return "$sexName$name${voice}叫了${numberArray[count]}聲,這是在下蛋"
    }
}      

然後在Activity中調用不同的類

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_test.setOnClickListener {
            //調用公雞類
            tv_result.text = Cock().callOut(count++ % 7)
        }

    }

}      
package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        btn_test.setOnClickListener {
            //調用母雞類
            tv_result.text = Hen().callOut(count++%7)
        }
        
    }

}      

運作結果

Kotlin學習日志(五)類與對象
Kotlin學習日志(五)類與對象

3.4 接口

Kotlin的接口與Java一樣是為了間接實作多重繼承,由于直接繼承多個類可能存在方法沖突等問題,是以Kotlin在編譯階段就不允許某個類同

時繼承多個基類,否則會報錯,于是,隻能通過接口定義幾個抽象方法,然後在實作該接口的具體類中重寫這幾個方法,進而間接實作類似C++多重繼承的功能。

在Kotlin中定義接口需要注意以下幾點:

(1)接口不能定義構造函數,否則編譯器會報錯"An interface may not have a constructor"。

(2)接口的内部方法通常要被實作它的類進行重寫,是以這些方法預設為抽象類型。

(3)與Java不同的是,Kotlin允許在接口内部實作某個方法,而Java接口的所有内部方法都必須是抽象方法。

Android開發中最常見的接口是控件的點選監聽器View.OnClickListener,它内部的點選動作onClick,類似的還有長按監聽器、選中監聽器等等,它們無一例外都定義了某種行為的事件處理過程。我們可以用一個列子來表達這些,比如鳥兒的飛翔、遊泳、奔跑等,下面定義一個行為接口 Behavior。

package com.llw.kotlinstart.custom_class

//Kotlin與Java一樣不允許多重繼承,即不能同時繼承兩個及兩個以上類
//否則編譯器報錯"Only one class may appear in a supertype list"
//是以仍然需要接口interface 來間接實作多重繼承的功能
//接口不能帶構造函數(那樣就變成一個類了),否則編譯器報錯"An interface may not have a constructor"
interface Behavior {
    //接口内部的方法預設就是抽象的,是以不加abstract 也可以,當然open也可以不加
    open abstract fun fly():String

    //比如下面這個swim方法就沒有加關鍵字abstract ,也無須在此實作方法
    fun swim():String

    //Kotlin的接口與Java的差別在于,Kotlin接口内部允許實作方法
    //此時該方法不是抽象方法,就不能加上abstract
    //不過該方法依然是open類型,接口内部的所有方法都預設是open類型
    fun run():String{
        return "大多數鳥兒跑得并不像樣,隻有鴕鳥、鸸鹋等少數鳥類才擅長奔跑。"

    }

    //Kotlin的接口允許聲明抽象屬性,實作該接口的類必須重載該屬性
    //與接口内部方法一樣,抽象屬性前面的open和abstract 也可以省略掉
    //open abstract var skiledSporte:String
    var skiledSporte:String

}      

在其他類實作這個接口時,跟類繼承一樣把接口名稱放在冒号後面,也就是說,Java的extends和implement這兩個關鍵字在Kotlin中都被冒号取代了。然後就想重寫抽象類的抽象方法一樣重寫接口的抽象方法,建立一個名為Goose的類,代碼如下:

package com.llw.kotlinstart.custom_class

class Goose(name: String = "鵝", sex: Int = Poultry.MALE) : Poultry(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 skiledSporte: String = "遊泳"

}      

然後在Activity中使用

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

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

    }

}      

運作效果如下圖:

調用fly方法

Kotlin學習日志(五)類與對象

調用swim方法

Kotlin學習日志(五)類與對象

調用run方法

Kotlin學習日志(五)類與對象

3.5 接口代理

通過接口固然完成了相應行為,但是鳥類這個家族非常龐大,如果每種鳥都實作Behavior接口,工作量是非常大的,其實鳥類的行為并不多,可以分類為飛禽、水禽、走禽三個行為類

下面是飛禽的行為類代碼示例:

package com.llw.kotlinstart.custom_class

class BehaviorFly : Behavior {
    override fun fly(): String {
        return "翺翔天空"
    }

    override fun swim(): String {
        return "落水鳳凰不如雞"
    }

    override fun run(): String {
        return "能飛幹嘛還要走"
    }

    override var skiledSporte: String = "飛翔"

}      

下面是水禽

package com.llw.kotlinstart.custom_class

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

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

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

    override var skiledSporte: String = "遊泳"

}      

下面是走禽

package com.llw.kotlinstart.custom_class

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

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

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

    override var skiledSporte: String = "奔跑"

}      

然後定義一個引用代理類的野禽基類,通過關鍵字by表示接口将由入參中的代理類實作,野禽基類WildFowl代碼如下:

package com.llw.kotlinstart.custom_class

//隻有接口才能使用關鍵字by進行代理操作
class WildFowl (name:String,sex:Int=MALE,behavior: Behavior):Poultry(name,sex),Behavior by behavior{
    
}      

然後在Activity中使用

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    var count: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_test.setOnClickListener {
            var fowl = when (count++ % 6) {
                //把代理類作為輸入參數來建立執行個體
                0 -> WildFowl("老鷹", Poultry.MALE, BehaviorFly())
                //由于sex字段是個預設參數,是以可通過命名參數給behavior指派
                1 -> WildFowl("鳳凰", behavior = BehaviorFly())
                2 -> WildFowl("大雁", Poultry.FEMALE, BehaviorSwim())
                3 -> WildFowl("企鵝", behavior = BehaviorSwim())
                4 -> WildFowl("鴕鳥", Poultry.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_result.text = "${fowl.name}: $action"
        }

    }

}      

運作效果如下:

老鷹的飛翔行為:

Kotlin學習日志(五)類與對象

鳳凰的遊泳行為:

Kotlin學習日志(五)類與對象

大雁的飛翔行為:

Kotlin學習日志(五)類與對象

企鵝的遊泳行為:

Kotlin學習日志(五)類與對象

鴕鳥的飛翔行為:

Kotlin學習日志(五)類與對象

鹂鵑的奔跑行為

Kotlin學習日志(五)類與對象

通過一頓操作之後,總結出Kotlin的類繼承與Java相比有所不同,主要展現在以下幾點:

(1)Kotlin的類預設不可被繼承,若需繼承,則要添加open聲明,而Java的類預設是允許被繼承的,隻有添加final聲明才表示不能為繼承。

(2)Kotlin除了正常的三個開放性修飾符public、protected、private外,另外增加了修飾符internal,表示隻對本子產品開放。

(3)Java的類繼承關鍵字extends以及接口實作關鍵字implement在Kotlin中都被冒号所取代。

(4)Kotlin允許在接口内部實作某個方法,而Java接口的内部方法隻能是抽象方法。

(5)Kotlin引入了接口代理(類代理)的概念,而Java不存在代理的寫法。

四、特殊類

4.1 嵌套類

一個類可以在單獨的代碼檔案中定義,也可以在另一個類内部定義,後一種情況叫作嵌套類,即A類嵌套在B類之中,聽起來和Java的嵌套類是一樣的,但其實有所差别,Java的嵌套類允許通路外部類的成員,而Kotlin的嵌套類不允許通路外部類的成員,強行通路則會報錯。

下面是示例代碼:

package com.llw.kotlinstart.custom_class

class Tree(var treeName: String) {
    //在類内部再定義一個類,這個新類稱作嵌套類
    class Flower(var flowerName: String) {
        fun getName(): String {
            return "這是一朵$flowerName"
            //普通的嵌套類不能通路外部類的成員,如treeName
            //否則編譯器會報錯:" Unresolved reference: *** "
            //return "這是${treeName}上的一朵$flowerName"
        }


    }
}      

調用嵌套類時,得在嵌套類的類名前面添加外部類的類名,相當于把這個嵌套類作為外部類的靜态對象使用,在Activity中調用代碼如下:

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_test.setOnClickListener {
            //使用嵌套類時,隻能引用外部類的類名,不能調用外部類的構造函數
            val peachBlossom = Tree.Flower("桃花")
            tv_result.text = peachBlossom.getName()
        }

    }

}      

因為嵌套類無法通路外部類的成員,是以其方法隻能傳回自身的資訊,運作效果圖如下:

Kotlin學習日志(五)類與對象

4.2 内部類

Kotlin限制了嵌套類不能通路外部類的成員,那還有什麼方法可以實作此功能呢?針對該問題,Kotlin另外增加了關鍵字inner表示内部,把inner加在嵌套類的class前面,然後嵌套類就變成了内部類,是以Kotlin的内部類就相當于Java的嵌套類,而Kotlin的嵌套類則是加了通路限制的内部類。還是在之前的嵌套類Tree中,加一個内部類Fruit ,示例代碼如下,

package com.llw.kotlinstart.custom_class

class Tree(var treeName: String) {
    //在類内部再定義一個類,這個新類稱作嵌套類
    class Flower(var flowerName: String) {
        fun getName(): String {
            return "這是一朵$flowerName"
            //普通的嵌套類不能通路外部類的成員,如treeName
            //否則編譯器會報錯:" Unresolved reference: *** "
            //return "這是${treeName}上的一朵$flowerName"
        }
        
    }
    
    //嵌套類加上inner字首,就變成内部類
    inner class Fruit(var fruitName:String){
        fun getName():String{
            //隻有聲明為内部類(添加了關鍵字inner,才能通路内外部類的成員)
            return  "這是${treeName}長出來的$fruitName"
        }
    }
}      

然後在Activity中調用

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btn_test.setOnClickListener {
            //使用嵌套類時,隻能引用外部類的類名,不能調用外部類的構造函數
            val peach = Tree("桃樹").Fruit("桃子")
            tv_result.text = peach.getName()
        }

    }

}      

運作效果圖:

Kotlin學習日志(五)類與對象

4.3 枚舉類

Java有一種枚舉類型,它采用關鍵字enum來表達,其内部定義了一系列名稱,通過有意義的名字比0、1、2這些數字能夠更有效地表達語義,下面是一個Java定義枚舉類型的代碼示例:

package com.llw.kotlinstart.custom_class;

enum Season {SPRING, SUMMER, AUTUMN, WINTER}      

再來看Kotlin的枚舉類

package com.llw.kotlinstart.custom_class

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

雖然看上去隻比Java的枚舉類型多了一個class,但是Kotlin中枚舉類内部的枚舉變量除了可以直接拿來指派之外,還可以通過枚舉值的幾個屬性獲得對應的資訊,例如ordinal屬性用于擷取該枚舉值的序号,name屬性用于擷取該枚舉值的名稱。枚舉變量本質上還是該類的一個執行個體,是以如果枚舉類存在構造函數,枚舉變量也必須調用對應的構造函數,這樣做的好處是,每一個枚舉值不但攜帶唯一的名稱,還可以擁有更加個性化的特征描述。下面建立一個枚舉類來說明,代碼如下:

package com.llw.kotlinstart.custom_class

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

然後在Activity中使用枚舉類SeasonType和SeasonName:

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var count: Int = 0
        btn_test.setOnClickListener {
            if (count % 2 == 0) {
                //ordinal表示枚舉類型的序号,name表示枚舉類型的名稱
                tv_result.text = when (count++ % 4) {
                    SeasonType.SPRING.ordinal -> SeasonType.SPRING.name
                    SeasonType.SUMMER.ordinal -> SeasonType.SUMMER.name
                    SeasonType.AUTUMN.ordinal -> SeasonType.AUTUMN.name
                    SeasonType.WINTER.ordinal -> SeasonType.WINTER.name
                    else -> "未知"
                }
            } else {
                tv_result.text = when(count++ % 4){
                    //使用自定義屬性seasonName表示個性化的描述
                    SeasonName.SPRING.ordinal -> SeasonName.SPRING.seasonName
                    SeasonName.SUMMER.ordinal -> SeasonName.SUMMER.seasonName
                    SeasonName.AUTUMN.ordinal -> SeasonName.AUTUMN.seasonName
                    SeasonName.WINTER.ordinal -> SeasonName.WINTER.seasonName
                    else -> "未知"
                    //枚舉類的構造函數是給枚舉類型使用的,外部不能直接調用枚舉類的構造函數
                }
            }
        }

    }

}      

4.4 密封類

為了解決枚舉值判斷的多餘分支問題,Kotlin提出了“密封類”得概念,密封類就像是一種更加嚴格的枚舉類,它内部有且僅有自身的執行個體對象,是以是一個有限的自身執行個體集合,或者說,密封類采用了嵌套類的手段,它的嵌套類全部由自身派生而來,定義密封類的時候,需要在該類的class前面加上關鍵字sealed作為标記。定義一個密封類,代碼如下:

package com.llw.kotlinstart.custom_class

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()

}      

這樣外部使用when語句便無須指定else分支了。下面是Activity中使用代碼

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var count: Int = 0
        btn_test.setOnClickListener {
            var season = when(count++%4){
                0 -> SeasonSealed.Spring("春天")
                1 -> SeasonSealed.Summer("夏天")
                2 -> SeasonSealed.Autumn("秋天")
                else -> SeasonSealed.Winter("冬天")
            }
            //密封類是一種嚴格的枚舉類,它的值是一個有限的集合
            //密封類確定條件分支覆寫了所有的枚舉類型,是以不再需要else分支
            tv_result.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 資料類

在Android實際開發中,我們經常需要定義一些實體類來存放傳回的資料,在Java中一個資料類的通常我完成以下工作:

(1)定義實體類的每個字段,以及對字段進行初始指派的構造函數。

(2)定義每個字段的get/set方法

(3)再判斷兩個資料對象是都相等時,通常每個字段都要比較一遍。

(4)在複制資料對象時,如果想另外修改某幾個字段值,得再補充對應數量的指派語句。

(5)在調試程式時,為獲知資料對象裡儲存的字段值,得手工把每個字段值都列印出來。

這對于開發者來說無疑于一個繁瑣的工作,而Kotlin鑒于此,推出了名為“資料類”這樣的騷操作,其實說起來也比較簡單,資料類的定義僅僅隻要在class前面增加關鍵字data,并聲明擁有完整輸入參數的構造函數,即可無縫實作以下功能:

(1)自動聲明與構造函數入參同名的屬性字段。

(2)自動實作每個屬性字段的get/set方法。

(3)自動提供equals方法,用于比較兩個資料對象是否相等。

(4)自動提供copy方法,允許完整指派某個資料對象,也可在複制後單獨修改某幾個字段的值。

(5)自動提供toString方法,用于列印資料對象中儲存的所有字段值。

說的這麼叼,也不知道是不是真的,來定義一個試一下吧。

代碼如下:

package com.llw.kotlinstart.custom_class
//資料類必須有主構造函數,且至少有一個輸入參數
//并且要聲明與輸入參數同名的屬性,即輸入參數前面添加關鍵字val或者var
//資料類不能是基類也不能是子類,不能是抽象類,也不能是内部類,更不能密封類
//我就是我,是顔色不一樣的煙火
data class Plant(
    var name: String,
    var stem: String,
    var leaf: String,
    var flower: String,
    var fruit: String,
    var seed: String
) {
}      

然後在Activity中調用一下吧

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var count: Int = 0
        
        var lotus = Plant("蓮","蓮藕","蓮葉","蓮花","蓮蓬","蓮子")
        //資料類的copy方法不帶參數,表示複制一模一樣的的對象
        var lotus2 = lotus.copy()
        btn_test.setOnClickListener {
            lotus2 = when(count++%2){
                //copy方法帶參數,表示指定參數另外指派
                0 -> lotus.copy(flower = "荷花")
                else -> lotus.copy(flower = "蓮花")
            }
            //資料類自帶equals方法,用于判斷兩個對象是否一樣
            var result = if(lotus2.equals(lotus)) "相等" else "不等"
            tv_result.text = "兩個植物的比較結果是${result}\n"+
                    "第一個植物的描述是${lotus.toString()}\n"+
                    "第二個植物的描述是${lotus2.toString()}"
        }

    }

}      

上述代碼調用了Plant對象的copy、equals、toString等方法,然後看一下運作的效果

Kotlin學習日志(五)類與對象
Kotlin學習日志(五)類與對象

4.6 模闆類

模闆類的應用相當廣泛,Kotlin中保留了它,而且寫法與Java類似,一樣在類名後面補充形如“”或者“<A,B>”這樣的表達式,表示此處的參數類型待定,要等建立類執行個體時再确定具體的參數類型,舉個例子,森林裡有一條小河,小河的長度可能以數字形式輸入(包括Int、Long、Float、Double),還有可能以字元串形式輸入。針對于這個需求編寫名為River的模闆類,代碼如下:

package com.llw.kotlinstart.custom_class
//在類名後面添加"<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"
    }
}      

然後在Activity中調用和這個模闆類

package com.llw.kotlinstart

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import com.llw.kotlinstart.custom_class.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var count: Int = 0

        btn_test.setOnClickListener {
            var river = when(count++%4){
                //模闆類(泛型類)聲明對象時,要在模闆類的類名後面加上"<參數類型>"
                0 -> River<Int>("小溪",100)
                //如果編譯器根據輸入參數就能知曉參數類型,也可直接省略"<參數類型>"
                1 -> River("瀑布",99.9f)
                2 -> River<Double>("山澗",50.5)
                //如果你已經是老手了,那麼怎麼舒服怎麼來,Kotlin的設計初衷就是偷懶
                else -> River("大河","一千")
            }
            tv_result.text = river.getInfo()
        }

    }

}      

運作效果圖如下:

小溪的長度

Kotlin學習日志(五)類與對象

瀑布的長度:

Kotlin學習日志(五)類與對象

山澗的長度:

Kotlin學習日志(五)類與對象

大河的長度:

Kotlin學習日志(五)類與對象